From 69783f6a201d5dcf92ad28dd00b8f3129183c6c6 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 3 Mar 2022 12:47:26 +0200 Subject: [PATCH 001/199] Add new script to run yoroi, ergo and cardano example --- package-lock.json | 45 +++++++++++++++++++ package.json | 4 +- .../example-ergo/package.json | 2 +- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 598271e671..37ae168c44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2026,6 +2026,33 @@ } } }, + "concurrently": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.0.0.tgz", + "integrity": "sha512-WKM7PUsI8wyXpF80H+zjHP32fsgsHNQfPLw/e70Z5dYkV7hF+rf8q3D+ScWJIEr57CpkO3OWBko6hwhQLPR8Pw==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "date-fns": "^2.16.1", + "lodash": "^4.17.21", + "rxjs": "^6.6.3", + "spawn-command": "^0.0.2-1", + "supports-color": "^8.1.0", + "tree-kill": "^1.2.2", + "yargs": "^16.2.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "config-chain": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", @@ -2216,6 +2243,12 @@ "assert-plus": "^1.0.0" } }, + "date-fns": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", + "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==", + "dev": true + }, "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -5552,6 +5585,12 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, + "spawn-command": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", + "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", + "dev": true + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -5884,6 +5923,12 @@ "punycode": "^2.1.1" } }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, "trim-newlines": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", diff --git a/package.json b/package.json index 6dca961468..8885e4e8ce 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "eslint": "lerna run eslint --stream", "flow": "lerna run flow --stream", "test": "lerna run test --stream", - "translations:purge": "lerna run translations:purge -- stream" + "translations:purge": "lerna run translations:purge -- stream", + "dev:all": "concurrently \"npm run dev:stable --prefix packages/yoroi-extension\" \"npm run cardano --prefix packages/yoroi-ergo-connector\" \"npm run ergo --prefix packages/yoroi-ergo-connector\" " }, "husky": { "hooks": { @@ -14,6 +15,7 @@ } }, "devDependencies": { + "concurrently": "^7.0.0", "husky": "4.3.8", "lerna": "^4.0.0" } diff --git a/packages/yoroi-ergo-connector/example-ergo/package.json b/packages/yoroi-ergo-connector/example-ergo/package.json index cbf3f61d10..890324a3bf 100644 --- a/packages/yoroi-ergo-connector/example-ergo/package.json +++ b/packages/yoroi-ergo-connector/example-ergo/package.json @@ -8,7 +8,7 @@ }, "scripts": { "build": "webpack --config webpack.config.js", - "start": "webpack-dev-server" + "start": "webpack-dev-server --host localhost --port 8081" }, "devDependencies": { "copy-webpack-plugin": "^5.0.0", From 001233fb4915a483f0e08eb1e1af8cb7a42d5968 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 3 Mar 2022 14:07:35 +0200 Subject: [PATCH 002/199] Display utxos to show which one to include in create tx --- .../example-cardano/index.html | 3 +- .../example-cardano/index.js | 150 ++++++++++++------ 2 files changed, 107 insertions(+), 46 deletions(-) diff --git a/packages/yoroi-ergo-connector/example-cardano/index.html b/packages/yoroi-ergo-connector/example-cardano/index.html index 0e192f6927..fdb788760c 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.html +++ b/packages/yoroi-ergo-connector/example-cardano/index.html @@ -46,7 +46,7 @@

Cardano dApp Example

- +
@@ -59,6 +59,7 @@

Cardano dApp Example

+
diff --git a/packages/yoroi-ergo-connector/example-cardano/index.js b/packages/yoroi-ergo-connector/example-cardano/index.js index 0257f73d6b..75912fb1fc 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.js +++ b/packages/yoroi-ergo-connector/example-cardano/index.js @@ -20,14 +20,16 @@ const isEnabledBtn = document.querySelector('#is-enabled') const getUtxos = document.querySelector('#get-utxos') const submitTx = document.querySelector('#submit-tx') const signTx = document.querySelector('#sign-tx') -const createTx = document.querySelector('#create-tx') +const showUtxos = document.querySelector('#show-utxos') const alertEl = document.querySelector('#alert') const spinner = document.querySelector('#spinner') +const utxosContainer = document.querySelector('#utxos') let accessGranted = false let cardanoApi let returnType = 'cbor' -let utxos +let utxos; +let selectedUtxoIdx = 0; let usedAddresses let changeAddress let unsignedTransactionHex @@ -456,8 +458,101 @@ signTx.addEventListener('click', () => { alertWarrning('Signing tx fails') }) }) +showUtxos.addEventListener('click', () => { + + if (!accessGranted) { + alertError('Should request access first'); + return; + } + + if (!utxos || utxos.length === 0) { + alertError('Should request utxos first'); + return + } + + hideAlert() + renderUtxo() +}) -createTx.addEventListener('click', () => { +function alertError (text) { + toggleSpinner('hide'); + alertEl.className = 'alert alert-danger' + alertEl.innerHTML = text +} + +function alertSuccess(text) { + alertEl.className = 'alert alert-success' + alertEl.innerHTML = text +} + +function hideAlert() { + alertEl.className = 'd-none' + alert.innerHTML = '' +} + +function alertWarrning(text) { + alertEl.className = 'alert alert-warning' + alertEl.innerHTML = text +} + +function toggleSpinner(status){ + if(status === 'show') { + spinner.className = 'spinner-border' + alertEl.className = 'd-none' + } else { + spinner.className = 'd-none' + } +} + +function toggleConnectionUI(status) { + if (status === 'button') { + connectionStatus.classList.add('d-none'); + cardanoAccessBtnRow.classList.remove('d-none'); + } else { + cardanoAccessBtnRow.classList.add('d-none'); + connectionStatus.classList.remove('d-none'); + } +} + +function selectUtxo(e) { + if (!e.target.id) { + alertError("Invalid idx") + return + } + selectedUtxoIdx = e.target.id + hideAlert() + renderUtxo() +} + +function renderUtxo() { + let utxosHTML = '' + for(let idx in utxos) { + const utxo = utxos[idx] + utxosHTML+= ` +
  • +

    ${utxo.utxo_id.slice(0, 50)}

    + ${utxo.amount} +
  • + ` + } + + utxosHTML += ` + + ` + utxosContainer.innerHTML = utxosHTML + utxosContainer.classList.remove('d-none') + utxosContainer.classList.add('d-block', 'list-group', 'list-group-numbered', 'mb-5') + // Add select utxo handler for each list item + document.querySelectorAll('.utxo-item').forEach(el => { + el.addEventListener('click', selectUtxo) + }) + + // Add event handler for create tx button + document.querySelector('#create-tx').addEventListener('click', createTxHandler) +} + + +function createTxHandler(e) { toggleSpinner('show'); if (!accessGranted) { @@ -475,13 +570,13 @@ createTx.addEventListener('click', () => { return } - const randomUtxo = utxos[Math.floor(Math.random() * utxos.length)]; - if (!randomUtxo) { + const selectedUtxo = utxos[selectedUtxoIdx]; + if (!selectedUtxo) { alertError('Failed to select a random utxo from the available list!'); return; } - console.log('[createTx] Including random utxo input: ', randomUtxo); + console.log('[createTx] Including random utxo input: ', selectedUtxo); const usedAddress = usedAddresses[0]; const keyHash = CardanoWasm.BaseAddress.from_address( @@ -518,18 +613,18 @@ createTx.addEventListener('click', () => { const outputHex = bytesToHex( CardanoWasm.TransactionOutput.new( - CardanoWasm.Address.from_bech32(randomUtxo.receiver), + CardanoWasm.Address.from_bech32(selectedUtxo.receiver), CardanoWasm.Value.new(CardanoWasm.BigNum.from_str('1000000')), ).to_bytes() ); const txReq = { validityIntervalStart: 42, - includeInputs: [randomUtxo.utxo_id], + includeInputs: [selectedUtxo.utxo_id], includeOutputs: [outputHex], includeTargets: [ { - address: randomUtxo.receiver, + address: selectedUtxo.receiver, value: '2000000', mintRequest: [{ script: mintScriptHex, @@ -565,7 +660,7 @@ createTx.addEventListener('click', () => { console.log('[createTx] Including asset:', asset); txReq.includeTargets.push({ // do not specify value, the connector will use minimum value - address: randomUtxo.receiver, + address: selectedUtxo.receiver, assets: { [asset.assetId]: '1', }, @@ -582,41 +677,6 @@ createTx.addEventListener('click', () => { toggleSpinner('hide') alertWarrning('Creating tx fails') }) -}) - -function alertError (text) { - toggleSpinner('hide'); - alertEl.className = 'alert alert-danger' - alertEl.innerHTML = text -} - -function alertSuccess(text) { - alertEl.className = 'alert alert-success' - alertEl.innerHTML = text -} - -function alertWarrning(text) { - alertEl.className = 'alert alert-warning' - alertEl.innerHTML = text -} - -function toggleSpinner(status){ - if(status === 'show') { - spinner.className = 'spinner-border' - alertEl.className = 'd-none' - } else { - spinner.className = 'd-none' - } -} - -function toggleConnectionUI(status) { - if (status === 'button') { - connectionStatus.classList.add('d-none'); - cardanoAccessBtnRow.classList.remove('d-none'); - } else { - cardanoAccessBtnRow.classList.add('d-none'); - connectionStatus.classList.remove('d-none'); - } } const onload = window.onload; From ce959d541bb98bb2dfe833b7d388becdb80935c3 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 3 Mar 2022 14:18:59 +0200 Subject: [PATCH 003/199] Handle content overflow inside alert --- packages/yoroi-ergo-connector/example-cardano/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-ergo-connector/example-cardano/index.js b/packages/yoroi-ergo-connector/example-cardano/index.js index 75912fb1fc..405f8d1991 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.js +++ b/packages/yoroi-ergo-connector/example-cardano/index.js @@ -476,12 +476,12 @@ showUtxos.addEventListener('click', () => { function alertError (text) { toggleSpinner('hide'); - alertEl.className = 'alert alert-danger' + alertEl.className = 'alert alert-danger overflow-scroll' alertEl.innerHTML = text } function alertSuccess(text) { - alertEl.className = 'alert alert-success' + alertEl.className = 'alert alert-success overflow-scroll' alertEl.innerHTML = text } @@ -670,7 +670,7 @@ function createTxHandler(e) { cardanoApi.experimental.createTx(txReq, true).then(txHex => { toggleSpinner('hide') - alertSuccess('Creating tx succeeds: ' + txHex) + alertSuccess(`

    Creating tx succeeds: ${txHex}

    `) unsignedTransactionHex = txHex }).catch(error => { console.error(error) From d344886fef6ffcb8d72e371a0cc6dbe63276983a Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 3 Mar 2022 14:20:29 +0200 Subject: [PATCH 004/199] Display json response inside pre html element --- packages/yoroi-ergo-connector/example-cardano/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-ergo-connector/example-cardano/index.js b/packages/yoroi-ergo-connector/example-cardano/index.js index 405f8d1991..9fab5db23d 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.js +++ b/packages/yoroi-ergo-connector/example-cardano/index.js @@ -187,7 +187,7 @@ getAccountBalance.addEventListener('click', () => { return res; }, {}); } - alertSuccess(`Account Balance: ${JSON.stringify(balanceJson, null, 2)}`) + alertSuccess(`Account Balance:

    ${JSON.stringify(balanceJson, null, 2)}
    `) }); } }) From c3da8496672c7d79e7541e5e48b33f2aee600389 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 3 Mar 2022 14:23:26 +0200 Subject: [PATCH 005/199] Center content --- packages/yoroi-ergo-connector/example-cardano/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-ergo-connector/example-cardano/index.html b/packages/yoroi-ergo-connector/example-cardano/index.html index fdb788760c..e2a5627727 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.html +++ b/packages/yoroi-ergo-connector/example-cardano/index.html @@ -6,7 +6,7 @@ Cardano Test dApp -
    +

    Cardano dApp Example

    From d1b279cb32ae87c6a1d7fc88f4fcfcf3e431afd0 Mon Sep 17 00:00:00 2001 From: yushi Date: Fri, 20 May 2022 19:04:06 +0800 Subject: [PATCH 006/199] add storage for new UTXO model --- .../app/api/ada/lib/storage/adaMigration.js | 9 + .../app/api/ada/lib/storage/database/index.js | 2 + .../ada/lib/storage/database/utxo/api/read.js | 98 +++++ .../lib/storage/database/utxo/api/write.js | 149 ++++++++ .../ada/lib/storage/database/utxo/tables.js | 127 +++++++ .../lib/storage/database/utxo/utxo.test.js | 353 ++++++++++++++++++ packages/yoroi-extension/package-lock.json | 156 ++------ packages/yoroi-extension/package.json | 3 +- 8 files changed, 765 insertions(+), 132 deletions(-) create mode 100644 packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js create mode 100644 packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js create mode 100644 packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js create mode 100644 packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js b/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js index 39442b6cac..e41ea1c1d6 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js @@ -111,6 +111,9 @@ export async function migrateToLatest( ['<3.9.6', async () => { return await ergoTxHistoryReset(persistentDb); }], + ['<4.14', async () => { + return await populateNewUtxodata(persistentDb); + }], ]; let appliedMigration = false; @@ -361,3 +364,9 @@ export async function ergoTxHistoryReset( return true; } + +export async function populateNewUtxodata( + persistentDb: lf$Database, +): Promise { + return true; +} diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js index 87ea8fd37c..0ab01811ab 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js @@ -28,6 +28,7 @@ import { populateWalletDb } from './walletTypes/core/tables'; import { populateMemoTransactionsDb } from './memos/tables'; import { populatePricesDb } from './prices/tables'; import { populateExplorerDb } from './explorers/tables'; +import { populateUtxoDb } from './utxo/tables'; import { KeyKind } from '../../../../common/lib/crypto/keys/types'; import { networks, defaultAssets } from './prepackaged/networks'; import { prepackagedExplorers } from './prepackaged/explorers'; @@ -205,6 +206,7 @@ const populateAndCreate = async ( populateMemoTransactionsDb(schemaBuilder); populatePricesDb(schemaBuilder); populateExplorerDb(schemaBuilder); + populateUtxoDb(schemaBuilder); const db = await schemaBuilder.connect({ storeType, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js new file mode 100644 index 0000000000..32eb4f1684 --- /dev/null +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js @@ -0,0 +1,98 @@ +// @flow + +import type { + lf$Database, + lf$Transaction, +} from 'lovefield'; +import { op } from 'lovefield'; +import { + getRowFromKey, getRowIn, +} from '../../utils'; +import * as Tables from '../tables'; +import type { + UtxoAtSafePoint, + UtxoAtSafePointRow, + UtxoDiffToBestBlock, + UtxoDiffToBestBlockRow, +} from '../tables'; + +export class GetUtxoAtSafePoint { + static ownTables: {| + UtxoAtSafePointTable: typeof Tables.UtxoAtSafePointSchema, + |} = Object.freeze({ + [Tables.UtxoAtSafePointSchema.name]: Tables.UtxoAtSafePointSchema, + }); + + static depTables: {||} = Object.freeze({}); + + static forWallet( + db: lf$Database, + tx: lf$Transaction, + conceptualWalletId: number, + ): Promise<$ReadOnly | void> { + return getRowFromKey( + db, tx, + conceptualWalletId, + GetUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].name, + GetUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].properties.ConceptualWalletId, + ); + } +} + +export class GetUtxoDiffToBestBlock { + static ownTables: {| + UtxoDiffToBestBlock: typeof Tables.UtxoDiffToBestBlockSchema, + |} = Object.freeze({ + [Tables.UtxoDiffToBestBlockSchema.name]: Tables.UtxoDiffToBestBlockSchema, + }); + + static depTables: {||} = Object.freeze({}); + + static async forWallet( + db: lf$Database, + tx: lf$Transaction, + conceptualWalletId: number, + ): Promise> { + const rows = await getRowIn( + db, tx, + GetUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name].name, + GetUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name].properties.ConceptualWalletId, + ([conceptualWalletId]: Array), + ); + return rows.map(r => ({ + lastBestBlockHash: r.lastBestBlockHash, + spentUtxoIds: r.spentUtxoIds, + newUtxos: r.newUtxos + })); + } + + // return only one object because there is an unique index on lastBestBlockHash + static async findLastBestBlockHash( + db: lf$Database, + tx: lf$Transaction, + conceptualWalletId: number, + lastBestBlockHash: string, + ): Promise<$ReadOnly | void> { + const schema = GetUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name]; + const table = db.getSchema().table(schema.name); + + const query = db + .select() + .from(table) + .where( + op.and( + table[schema.properties.ConceptualWalletId].eq(conceptualWalletId), + table[schema.properties.lastBestBlockHash].eq(lastBestBlockHash) + ) + ); + const rows = await tx.attach(query); + if (rows.length === 0) { + return undefined; + } + return { + lastBestBlockHash: rows[0].lastBestBlockHash, + spentUtxoIds: rows[0].spentUtxoIds, + newUtxos: rows[0].newUtxos + }; + } +} diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js new file mode 100644 index 0000000000..fb4832799c --- /dev/null +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js @@ -0,0 +1,149 @@ +// @flow + +import type { + lf$Database, + lf$Transaction, +} from 'lovefield'; +import { op } from 'lovefield'; +import { + addOrReplaceRow, addNewRowToTable, removeFromTableBatch, +} from '../../utils'; +import * as Tables from '../tables'; +import type { + UtxoAtSafePoint, + UtxoAtSafePointInsert, + UtxoAtSafePointRow, + UtxoDiffToBestBlock, + UtxoDiffToBestBlockInsert, + UtxoDiffToBestBlockRow, +} from '../tables'; +import { GetUtxoAtSafePoint, GetUtxoDiffToBestBlock } from './read'; + +export class ModifyUtxoAtSafePoint { + static ownTables: {| + UtxoAtSafePointTable: typeof Tables.UtxoAtSafePointSchema, + |} = Object.freeze({ + [Tables.UtxoAtSafePointSchema.name]: Tables.UtxoAtSafePointSchema, + }); + + static depTables: {||} = Object.freeze({}); + + static async addOrReplace( + db: lf$Database, + tx: lf$Transaction, + conceptualWalletId: number, + utxoAtSafePoint: UtxoAtSafePoint + ): Promise { + const row = await GetUtxoAtSafePoint.forWallet(db, tx, conceptualWalletId); + if (row) { + const newRow: UtxoAtSafePointRow = { + UtxoAtSafePointId: row.UtxoAtSafePointId, + ConceptualWalletId: conceptualWalletId, + UtxoAtSafePoint: utxoAtSafePoint, + }; + await addOrReplaceRow( + db, tx, + newRow, + ModifyUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].name, + ); + } else { + await addNewRowToTable( + db, tx, + { + ConceptualWalletId: conceptualWalletId, + UtxoAtSafePoint: utxoAtSafePoint, + }, + ModifyUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].name, + ); + } + } + + static async remove( + db: lf$Database, + tx: lf$Transaction, + conceptualWalletId: number, + ): Promise { + await removeFromTableBatch( + db, tx, + ModifyUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].name, + ModifyUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].properties.ConceptualWalletId, + ([conceptualWalletId]: Array), + ); + } +} + +export class ModifyUtxoDiffToBestBlock { + static ownTables: {| + UtxoDiffToBestBlock: typeof Tables.UtxoDiffToBestBlockSchema, + |} = Object.freeze({ + [Tables.UtxoDiffToBestBlockSchema.name]: Tables.UtxoDiffToBestBlockSchema, + }); + + static depTables: {||} = Object.freeze({}); + + static async removeAll( + db: lf$Database, + tx: lf$Transaction, + conceptualWalletId: number, + ): Promise { + const schema = ModifyUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name]; + const tableName = schema.name; + const fieldNames = schema.properties; + const table = db.getSchema().table(tableName); + await tx.attach( + db.delete().from(table) + .where(table[fieldNames.ConceptualWalletId].eq(conceptualWalletId)) + ); + } + + static async remove( + db: lf$Database, + tx: lf$Transaction, + conceptualWalletId: number, + lastBestBlockHash: string, + ): Promise { + const schema = ModifyUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name]; + const tableName = schema.name; + const fieldNames = schema.properties; + const table = db.getSchema().table(tableName); + await tx.attach( + db.delete().from(table) + .where( + op.and( + table[fieldNames.ConceptualWalletId].eq(conceptualWalletId), + table[fieldNames.lastBestBlockHash].eq(lastBestBlockHash) + ) + ) + ); + } + + static async add( + db: lf$Database, + tx: lf$Transaction, + conceptualWalletId: number, + utxoDiffToBestBlock: UtxoDiffToBestBlock, + ): Promise { + // Do nothing if a row with `utxoDiffToBestBlock.lastBestBlockHash` is already + // present. But we can't rely on the unique index because the exception is + // thrown when the tx is being committed and there is no way to catch it + // only for this query. + const existing = await GetUtxoDiffToBestBlock.findLastBestBlockHash( + db, tx, + conceptualWalletId, + utxoDiffToBestBlock.lastBestBlockHash + ); + + if (!existing) { + await addNewRowToTable( + db, tx, + { + ConceptualWalletId: conceptualWalletId, + lastBestBlockHash: utxoDiffToBestBlock.lastBestBlockHash, + spentUtxoIds: utxoDiffToBestBlock.spentUtxoIds, + newUtxos: utxoDiffToBestBlock.newUtxos, + }, + ModifyUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name].name, + ); + } + } +} diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js new file mode 100644 index 0000000000..23d3b2804d --- /dev/null +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js @@ -0,0 +1,127 @@ +// @flow +import BigNumber from 'bignumber.js'; +import { Type, ConstraintAction, } from 'lovefield'; +import type { lf$schema$Builder } from 'lovefield'; +import { ConceptualWalletSchema } from '../walletTypes/core/tables'; + +type Asset = {| + assetId: string, + policyId: string, + name: string, + amount: string, +|}; + +export type Utxo = {| + utxoId: string, + txHash: string, + txIndex: number, + receiver: string, + amount: string, + assets: Array, + blockNum: number, +|}; + +export type UtxoAtSafePoint = {| + lastSafeBlockHash: string, + utxos: Array, +|}; + +export type UtxoDiffToBestBlock = {| + lastBestBlockHash: string, + spentUtxoIds: Array, + newUtxos: Array, +|}; + +// DB schema: +export type UtxoAtSafePointInsert = {| + ConceptualWalletId: number, + UtxoAtSafePoint: UtxoAtSafePoint, +|}; +export type UtxoAtSafePointRow = {| + UtxoAtSafePointId: number, // serial + ...UtxoAtSafePointInsert, +|}; +export const UtxoAtSafePointSchema: {| + +name: 'UtxoAtSafePointTable', + properties: $ObjMapi, +|} = { + name: 'UtxoAtSafePointTable', + properties: { + UtxoAtSafePointId: 'UtxoAtSafePointId', + ConceptualWalletId: 'ConceptualWalletId', + UtxoAtSafePoint: 'UtxoAtSafePoint', + }, +}; + +export type UtxoDiffToBestBlockInsert = {| + ConceptualWalletId: number, + // we need to index into the `lastBestBlockHash` field, so we have to spread it + ...UtxoDiffToBestBlock, +|}; +export type UtxoDiffToBestBlockRow = {| + UtxoDiffToBestBlockId: number, // serial + ...UtxoDiffToBestBlockInsert, +|}; +export const UtxoDiffToBestBlockSchema: {| + +name: 'UtxoDiffToBestBlock', + properties: $ObjMapi, +|} = { + name: 'UtxoDiffToBestBlock', + properties: { + UtxoDiffToBestBlockId: 'UtxoDiffToBestBlockId', + ConceptualWalletId: 'ConceptualWalletId', + lastBestBlockHash: 'lastBestBlockHash', + spentUtxoIds: 'spentUtxoIds', + newUtxos: 'newUtxos', + }, +}; + +export const populateUtxoDb = (schemaBuilder: lf$schema$Builder) => { + schemaBuilder.createTable(UtxoAtSafePointSchema.name) + .addColumn(UtxoAtSafePointSchema.properties.UtxoAtSafePointId, Type.INTEGER) + .addColumn(UtxoAtSafePointSchema.properties.ConceptualWalletId, Type.INTEGER) + .addColumn(UtxoAtSafePointSchema.properties.UtxoAtSafePoint, Type.OBJECT) + .addPrimaryKey( + ([UtxoAtSafePointSchema.properties.UtxoAtSafePointId]: Array), + true + ) + .addForeignKey('UtxoAtSafePoint_ConceptualWallet', { + local: UtxoAtSafePointSchema.properties.ConceptualWalletId, + ref: `${ConceptualWalletSchema.name}.${ConceptualWalletSchema.properties.ConceptualWalletId}` + }) + .addIndex( + 'UtxoAtSafePoint_ConceptualWallet_Index', + ([UtxoAtSafePointSchema.properties.ConceptualWalletId]: Array), + false + ); + + schemaBuilder.createTable(UtxoDiffToBestBlockSchema.name) + .addColumn(UtxoDiffToBestBlockSchema.properties.UtxoDiffToBestBlockId, Type.INTEGER) + .addColumn(UtxoDiffToBestBlockSchema.properties.ConceptualWalletId, Type.INTEGER) + .addColumn(UtxoDiffToBestBlockSchema.properties.lastBestBlockHash, Type.STRING) + .addColumn(UtxoDiffToBestBlockSchema.properties.spentUtxoIds, Type.OBJECT) + .addColumn(UtxoDiffToBestBlockSchema.properties.newUtxos, Type.OBJECT) + .addPrimaryKey( + ([UtxoDiffToBestBlockSchema.properties.UtxoDiffToBestBlockId]: Array), + true + ) + .addForeignKey('UtxoDiffToBestBlock_ConceptualWallet', { + local: UtxoDiffToBestBlockSchema.properties.ConceptualWalletId, + ref: `${ConceptualWalletSchema.name}.${ConceptualWalletSchema.properties.ConceptualWalletId}` + }) + .addIndex( + 'UtxoDiffToBestBlock_ConceptualWallet_Index', + ([UtxoDiffToBestBlockSchema.properties.ConceptualWalletId]: Array), + false + ) + .addIndex( + 'UtxoDiffToBestBlock_ConceptualWallet_lastBestBlockHash_Index', + ( + [ + UtxoDiffToBestBlockSchema.properties.ConceptualWalletId, + UtxoDiffToBestBlockSchema.properties.lastBestBlockHash, + ]: Array + ), + true, + ); +}; diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js new file mode 100644 index 0000000000..52f6310492 --- /dev/null +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js @@ -0,0 +1,353 @@ +// @flow + +import { schema, } from 'lovefield'; +import { loadLovefieldDB } from '../index'; +import { GetUtxoAtSafePoint, GetUtxoDiffToBestBlock } from './api/read'; +import { ModifyConceptualWallet } from '../walletTypes/core/api/write'; +import { ModifyUtxoAtSafePoint, ModifyUtxoDiffToBestBlock } from './api/write'; +import { getAllSchemaTables, raii } from '../utils'; + +const CONCEPTUAL_WALLET = { + Name: '', + NetworkId: 1, +}; + +const UTXO_AT_SAFE_BLOCK_1 = { + lastSafeBlockHash: 'lastSafeBlockHash1', + utxos: [ + { + utxoId: 'utxoId1', + txHash: 'txHash1', + txIndex: 0, + receiver: 'receiver1', + amount: '42', + assets: [], + blockNum: 1 + } + ], +}; + +const UTXO_AT_SAFE_BLOCK_2 = { + lastSafeBlockHash: 'lastSafeBlockHash2', + utxos: [ + { + utxoId: 'utxoId2', + txHash: 'txHash2', + txIndex: 0, + receiver: 'receiver2', + amount: '42', + assets: [], + blockNum: 2 + } + ], +}; + +const UTXO_DIFF_TO_BEST_BLOCK_1 = { + lastBestBlockHash: 'lastBestBlockHash1', + spentUtxoIds: ['utxoId1'], + newUtxos: [ + { + utxoId: 'utxoId3', + txHash: 'txHash3', + txIndex: 0, + receiver: 'receiver3', + amount: '42', + assets: [], + blockNum: 3 + } + ], +}; + +const UTXO_DIFF_TO_BEST_BLOCK_2 = { + lastBestBlockHash: 'lastBestBlockHash2', + spentUtxoIds: ['utxoId2'], + newUtxos: [ + { + utxoId: 'utxoId4', + txHash: 'txHash4', + txIndex: 0, + receiver: 'receiver4', + amount: '42', + assets: [], + blockNum: 4 + } + ], +}; + +let db; +let conceptualWalletId; + +beforeAll(async () => { + db = await loadLovefieldDB(schema.DataStoreType.MEMORY); + + const result = await raii( + db, + getAllSchemaTables(db, ModifyConceptualWallet), + async tx => ModifyConceptualWallet.add( + db, + tx, + CONCEPTUAL_WALLET, + ) + ); + conceptualWalletId = result.ConceptualWalletId; +}); + +test('UtxoAtSafePoint', async () => { + // add + await raii( + db, + getAllSchemaTables(db, ModifyUtxoAtSafePoint), + async tx => { + await ModifyUtxoAtSafePoint.addOrReplace( + db, + tx, + conceptualWalletId, + UTXO_AT_SAFE_BLOCK_1, + ); + } + ); + + // check + let result = await raii( + db, + getAllSchemaTables(db, GetUtxoAtSafePoint), + tx => GetUtxoAtSafePoint.forWallet( + db, + tx, + conceptualWalletId, + ) + ); + + expect(result).toEqual( + expect.objectContaining({ UtxoAtSafePoint: UTXO_AT_SAFE_BLOCK_1 }) + ); + + // replace + await raii( + db, + getAllSchemaTables(db, ModifyUtxoAtSafePoint), + async tx => { + await ModifyUtxoAtSafePoint.addOrReplace( + db, + tx, + conceptualWalletId, + UTXO_AT_SAFE_BLOCK_2, + ); + } + ); + + // check + result = await raii( + db, + getAllSchemaTables(db, GetUtxoAtSafePoint), + tx => GetUtxoAtSafePoint.forWallet( + db, + tx, + conceptualWalletId, + ) + ); + + expect(result).toEqual( + expect.objectContaining({ UtxoAtSafePoint: UTXO_AT_SAFE_BLOCK_2 }) + ); + + // remove + await raii( + db, + getAllSchemaTables(db, ModifyUtxoAtSafePoint), + async tx => { + await ModifyUtxoAtSafePoint.remove( + db, + tx, + conceptualWalletId, + ); + } + ); + + // check + result = await raii( + db, + getAllSchemaTables(db, GetUtxoAtSafePoint), + tx => GetUtxoAtSafePoint.forWallet( + db, + tx, + conceptualWalletId, + ) + ); + + expect(result).toBe(undefined); +}); + +test('UtxoDiffToBestBlock', async () => { + // initially empty + let result = await raii( + db, + getAllSchemaTables(db, GetUtxoDiffToBestBlock), + tx => GetUtxoDiffToBestBlock.forWallet( + db, + tx, + conceptualWalletId, + ) + ); + + expect(result).toEqual([]); + + // add + await raii( + db, + getAllSchemaTables(db, ModifyUtxoDiffToBestBlock), + async tx => { + await ModifyUtxoDiffToBestBlock.add( + db, + tx, + conceptualWalletId, + UTXO_DIFF_TO_BEST_BLOCK_1, + ); + } + ); + + // check + result = await raii( + db, + getAllSchemaTables(db, GetUtxoDiffToBestBlock), + tx => GetUtxoDiffToBestBlock.forWallet( + db, + tx, + conceptualWalletId, + ) + ); + + expect(result).toEqual([UTXO_DIFF_TO_BEST_BLOCK_1]); + + result = await raii( + db, + getAllSchemaTables(db, GetUtxoDiffToBestBlock), + tx => GetUtxoDiffToBestBlock.findLastBestBlockHash( + db, + tx, + conceptualWalletId, + UTXO_DIFF_TO_BEST_BLOCK_1.lastBestBlockHash, + ) + ); + + expect(result).toEqual(UTXO_DIFF_TO_BEST_BLOCK_1); + + // add duplicate + await raii( + db, + getAllSchemaTables(db, ModifyUtxoDiffToBestBlock), + async tx => { + await ModifyUtxoDiffToBestBlock.add( + db, + tx, + conceptualWalletId, + UTXO_DIFF_TO_BEST_BLOCK_1, + ); + } + ); + + // check + result = await raii( + db, + getAllSchemaTables(db, GetUtxoDiffToBestBlock), + tx => GetUtxoDiffToBestBlock.forWallet( + db, + tx, + conceptualWalletId, + ) + ); + + expect(result).toEqual([UTXO_DIFF_TO_BEST_BLOCK_1]); + + // remove + await raii( + db, + getAllSchemaTables(db, ModifyUtxoDiffToBestBlock), + async tx => { + await ModifyUtxoDiffToBestBlock.remove( + db, + tx, + conceptualWalletId, + UTXO_DIFF_TO_BEST_BLOCK_1.lastBestBlockHash, + ); + } + ); + + // check + result = await raii( + db, + getAllSchemaTables(db, GetUtxoDiffToBestBlock), + tx => GetUtxoDiffToBestBlock.forWallet( + db, + tx, + conceptualWalletId, + ) + ); + + expect(result).toEqual([]); + + // add two + await raii( + db, + getAllSchemaTables(db, ModifyUtxoDiffToBestBlock), + async tx => { + await ModifyUtxoDiffToBestBlock.add( + db, + tx, + conceptualWalletId, + UTXO_DIFF_TO_BEST_BLOCK_1, + ); + } + ); + await raii( + db, + getAllSchemaTables(db, ModifyUtxoDiffToBestBlock), + async tx => { + await ModifyUtxoDiffToBestBlock.add( + db, + tx, + conceptualWalletId, + UTXO_DIFF_TO_BEST_BLOCK_2, + ); + } + ); + + // check + result = await raii( + db, + getAllSchemaTables(db, GetUtxoDiffToBestBlock), + tx => GetUtxoDiffToBestBlock.forWallet( + db, + tx, + conceptualWalletId, + ) + ); + + expect(result).toEqual([UTXO_DIFF_TO_BEST_BLOCK_1, UTXO_DIFF_TO_BEST_BLOCK_2]); + + // remove all + await raii( + db, + getAllSchemaTables(db, ModifyUtxoDiffToBestBlock), + async tx => { + await ModifyUtxoDiffToBestBlock.removeAll( + db, + tx, + conceptualWalletId, + ); + } + ); + + // check + result = await raii( + db, + getAllSchemaTables(db, GetUtxoDiffToBestBlock), + tx => GetUtxoDiffToBestBlock.forWallet( + db, + tx, + conceptualWalletId, + ) + ); + + expect(result).toEqual([]); +}); diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index ffbfad2bf1..58d8ae2277 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -1721,6 +1721,26 @@ "@cardano-foundation/ledgerjs-hw-app-cardano": "5.0.0" } }, + "@emurgo/yoroi-lib-core": { + "version": "0.4.1-alpha.28", + "resolved": "https://registry.npmjs.org/@emurgo/yoroi-lib-core/-/yoroi-lib-core-0.4.1-alpha.28.tgz", + "integrity": "sha512-jBz36hGdTrS36nskV00W6GiVNxpq8nojgqS37ySI2U12uYZ0v9imWDl64nyt5IBrG9uP+sK13RM+lrRZsLNFRQ==", + "requires": { + "axios": "^0.24.0", + "bignumber.js": "^9.0.1", + "hash-wasm": "^4.9.0" + }, + "dependencies": { + "axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "requires": { + "follow-redirects": "^1.14.4" + } + } + } + }, "@eslint/eslintrc": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", @@ -7437,12 +7457,6 @@ "defer-to-connect": "^2.0.0" } }, - "@testim/chrome-version": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.2.tgz", - "integrity": "sha512-1c4ZOETSRpI0iBfIFUqU4KqwBAB2lHUAlBjZz/YqOHqwM9dTTzjV6Km0ZkiEiSCx/tLr1BtESIKyWWMww+RUqw==", - "dev": true - }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -7994,16 +8008,6 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, - "@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -10596,32 +10600,6 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, - "chromedriver": { - "version": "100.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-100.0.0.tgz", - "integrity": "sha512-oLfB0IgFEGY9qYpFQO/BNSXbPw7bgfJUN5VX8Okps9W2qNT4IqKh5hDwKWtpUIQNI6K3ToWe2/J5NdpurTY02g==", - "dev": true, - "requires": { - "@testim/chrome-version": "^1.1.2", - "axios": "^0.24.0", - "del": "^6.0.0", - "extract-zip": "^2.0.1", - "https-proxy-agent": "^5.0.0", - "proxy-from-env": "^1.1.0", - "tcp-port-used": "^1.0.1" - }, - "dependencies": { - "axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", - "dev": true, - "requires": { - "follow-redirects": "^1.14.4" - } - } - } - }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -12477,22 +12455,6 @@ } } }, - "del": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", - "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", - "dev": true, - "requires": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -14381,29 +14343,6 @@ } } }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } - } - }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -16175,6 +16114,11 @@ "safe-buffer": "^5.2.0" } }, + "hash-wasm": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.9.0.tgz", + "integrity": "sha512-7SW7ejyfnRxuOc7ptQHSf4LDoZaWOivfzqw+5rpcQku0nHfmicPKE51ra9BiRLAmT8+gGLestr1XroUkqdjL6w==" + }, "hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -16730,12 +16674,6 @@ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, - "ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "dev": true - }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -17036,12 +16974,6 @@ "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", "dev": true }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -17147,12 +17079,6 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true - }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -17191,17 +17117,6 @@ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, - "is2": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.7.tgz", - "integrity": "sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" - } - }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -26000,27 +25915,6 @@ "readable-stream": "^3.1.1" } }, - "tcp-port-used": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", - "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", - "dev": true, - "requires": { - "debug": "4.3.1", - "is2": "^2.0.6" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - } - } - }, "telejson": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/telejson/-/telejson-5.3.3.tgz", diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index 7a3e68fcbc..873965ec19 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.13.100", + "version": "4.14.0", "description": "Cardano ADA wallet", "scripts": { "dev:build": "rimraf dev/ && babel-node scripts/build --type=debug", @@ -176,6 +176,7 @@ "@emurgo/cip4-js": "1.0.5", "@emurgo/js-chain-libs": "0.7.1", "@emurgo/ledger-connect-handler": "7.0.1", + "@emurgo/yoroi-lib-core": "^0.4.1-alpha.28", "@mui/lab": "^5.0.0-alpha.51", "@mui/material": "^5.0.4", "@svgr/webpack": "5.5.0", From 985558f05b168baebdb74d79132e6a5f806ff252 Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 23 May 2022 17:31:51 +0800 Subject: [PATCH 007/199] use Yoroi-lib UtxoService to update UTXO state --- .../api/ada/lib/state-fetch/mockNetwork.js | 220 ++++++ .../app/api/ada/lib/state-fetch/utxoApi.js | 18 + .../ada/lib/storage/bridge/tests/utxo.test.js | 630 ++++++++++++++++++ .../lib/storage/bridge/updateTransactions.js | 95 ++- .../lib/storage/models/PublicDeriver/index.js | 17 +- .../app/api/ada/lib/storage/models/utils.js | 162 +++++ 6 files changed, 1114 insertions(+), 28 deletions(-) create mode 100644 packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js create mode 100644 packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js index 03abd155cd..ea3e7ef2be 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js @@ -37,6 +37,20 @@ import { networks, getCardanoHaskellBaseConfig } from '../storage/database/prepa import { bech32 } from 'bech32'; import { Bech32Prefix } from '../../../../config/stringConfig'; import { parseTokenList } from '../../transactions/utils'; +import type { UtxoApiContract } from '@emurgo/yoroi-lib-core/dist/utxo/api'; +import type { + Asset, + DiffPoint, + TipStatusReference, + Utxo, + UtxoApiResponse, + UtxoAtPointRequest, + UtxoDiff, + UtxoDiffItem, + UtxoDiffItemOutput, + UtxoDiffSincePointRequest +} from '@emurgo/yoroi-lib-core/dist/utxo/models'; +import { UtxoApiResult, } from '@emurgo/yoroi-lib-core/dist/utxo/models'; /** convert bech32 address to bytes */ function fixAddresses( @@ -678,3 +692,209 @@ export function genGetMultiAssetMetadata( ): MultiAssetMintMetadataFunc { return async (_) => ({}); } + + +export class MockUtxoApi implements UtxoApiContract { + blockchain: Array; + lastSafeBlockTxIndex: number; + + constructor( + blockchain: Array, + safeConfirmations: number, + ) { + this.blockchain = blockchain; + + let blockCount = 0; + let currentBlockHash = blockchain[blockchain.length - 1].block_hash; + let i; + for (i = blockchain.length - 1; i >= 0; i --) { + if (blockchain[i].block_hash !== currentBlockHash) { + blockCount += 1; + if (blockCount = safeConfirmations) { + break; + } else { + currentBlockHash = blockchain[i].block_hash; + } + } + } + if (i === -1) { + throw new Error('not enough blocks for a safe block'); + } else { + this.lastSafeBlockTxIndex = i; + } + } + + async getBestBlock(): Promise { + const hash = this.blockchain[this.blockchain.length - 1].block_hash; + if (!hash) { + throw new Error('expect hash'); + } + return hash; + } + + async getSafeBlock(): Promise { + const hash = this.blockchain[this.lastSafeBlockTxIndex].block_hash; + if (!hash) { + throw new Error('expect hash'); + } + return hash; + } + + async getTipStatusWithReference( + bestBlocks: string[] + ): Promise> { + for (let i = this.blockchain.length - 1; i >= 0; i--) { + const hash = this.blockchain[i].block_hash; + if (!hash) { + throw new Error('expect hash'); + } + const height = this.blockchain[i].height; + if (height == null) { + throw new Error('expect height'); + } + if (bestBlocks.includes(hash)) { + const safeBlockHash = this.blockchain[this.lastSafeBlockTxIndex].block_hash; + if (!safeBlockHash) { + throw new Error('expect hash'); + } + const safeBlockHeight = this.blockchain[this.lastSafeBlockTxIndex].height; + if (safeBlockHeight == null) { + throw new Error('expect block height'); + } + return { + result: UtxoApiResult.SUCCESS, + value: { + reference: { + lastFoundBestBlock: hash, + lastFoundSafeBlock: (safeBlockHeight >= height) + ? safeBlockHash + : hash, + } + } + }; + } + } + return { + result: UtxoApiResult.SAFEBLOCK_ROLLBACK + }; + } + + async getUtxoAtPoint(req: UtxoAtPointRequest): Promise> { + const { addresses, referenceBlockHash } = req; + let lastTxIndex; + for (lastTxIndex = this.blockchain.length - 1; lastTxIndex >= 0; lastTxIndex--) { + const hash = this.blockchain[lastTxIndex].block_hash; + if (hash === referenceBlockHash) { + break; + } + } + if (lastTxIndex === -1) { + throw new Error('block not found'); + } + let utxos = []; + for (let i = 0; i <= lastTxIndex; i++) { + const tx = this.blockchain[i]; + // remove spent + utxos = utxos.filter( + utxo => !tx.inputs.some( + input => input.txHash === utxo.txHash && input.index === utxo.txIndex + ) + ); + // add new + tx.outputs.filter( + ({ address }) => addresses.includes(address) + ).forEach((output, outputIndex) => { + const { height } = tx; + if (height == null) { + throw new Error('expect height'); + } + utxos.push({ + utxoId: `${tx.hash}${outputIndex}`, + txHash: tx.hash, + txIndex: outputIndex, + receiver: output.address, + amount: new BigNumber(output.amount), + assets: output.assets.map(asset => ({ + assetId: asset.assetId, + policyId: asset.policyId, + name: asset.name, + amount: asset.amount, + })), + blockNum: height, + }); + }); + } + return { + result: UtxoApiResult.SUCCESS, + value: utxos, + }; + } + + async getUtxoDiffSincePoint(req: UtxoDiffSincePointRequest): Promise> { + const { addresses, untilBlockHash, afterBestBlock, } = req; + let seenBestBlock = false; + let seenUntilBlock = false; + let utxoDiffItems = []; + for (let i = 0; i <= this.blockchain.length; i++) { + const tx = this.blockchain[i]; + if (seenBestBlock) { + if (tx.block_hash === afterBestBlock) { + continue; + } + if (tx.block_hash === untilBlockHash) { + seenUntilBlock = true; + break; + } + tx.outputs.filter( + ({ address }) => addresses.includes(address) + ).forEach((output, outputIndex) => { + const utxoId = `${tx.hash}${outputIndex}` + utxoDiffItems.push( + { + type: 'output', + id: utxoId, + amount: new BigNumber(output.amount), + utxo:{ + utxoId, + txHash: tx.hash, + txIndex: outputIndex, + receiver: output.address, + amount: new BigNumber(output.amount), + assets: output.assets.map(asset => ({ + assetId: asset.assetId, + policyId: asset.policyId, + name: asset.name, + amount: asset.amount, + })), + blockNum: tx.height, + } + } + ); + }); + tx.inputs.filter(input => addresses.includes(input.address)) + .forEach(input => { + utxoDiffItems.push( + ({ + type: 'input', + id: input.id, + amount: new BigNumber(input.amount), + }: UtxoDiffItem) + ); + }); + } else { + if (tx.block_hash === afterBestBlock) { + seenBestBlock = true; + } + } + } + if (!seenUntilBlock) { + return { + result: UtxoApiResult.BESTBLOCK_ROLLBACK + }; + } + return { + result: UtxoApiResult.SUCCESS, + value: { diffItems: utxoDiffItems }, + }; + } +} diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js new file mode 100644 index 0000000000..cb4c6d3faf --- /dev/null +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js @@ -0,0 +1,18 @@ +// @flow +import axios from 'axios'; +import { + BatchedEmurgoUtxoApi, + EmurgoUtxoApi +} from '@emurgo/yoroi-lib-core/dist/utxo/emurgo-api'; +import type { UtxoApiContract } from '@emurgo/yoroi-lib-core/dist/utxo/api'; + +export default class UtxoApi extends BatchedEmurgoUtxoApi { + // so that the unit tests can override it with mocks + static utxoApiFactory: (string) => UtxoApiContract = (backendServiceUrl) => + new EmurgoUtxoApi(axios, backendServiceUrl + '/', true); + + constructor(backendServiceUrl: string) { + const utxoApi = UtxoApi.utxoApiFactory(backendServiceUrl); + super(utxoApi); + } +} diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js new file mode 100644 index 0000000000..123986c6b6 --- /dev/null +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js @@ -0,0 +1,630 @@ +// @flow + +import BigNumber from 'bignumber.js'; +import { + schema, +} from 'lovefield'; +import '../../../test-config'; +import type { RemoteTransaction } from '../../../state-fetch/types'; +import { + setup, +} from './common'; +import { + ABANDON_SHARE, + TX_TEST_MNEMONIC_1, + mockDate, + filterDbSnapshot, +} from '../../../../../jestUtils'; +import { + genCheckAddressesInUse, + genGetTransactionsHistoryForAddresses, + genGetBestBlock, + getSingleAddressString, + genGetTokenInfo, + genGetMultiAssetMetadata, + MockUtxoApi, +} from '../../../state-fetch/mockNetwork'; +import { + HARD_DERIVATION_START, + WalletTypePurpose, + CoinTypes, + ChainDerivations, +} from '../../../../../../config/numbersConfig'; +import type { WalletTypePurposeT } from '../../../../../../config/numbersConfig'; +import { loadLovefieldDB } from '../../database/index'; + +import { + asGetAllUtxos, + asDisplayCutoff, + asGetUtxoBalance, +} from '../../models/PublicDeriver/traits'; + +import { + updateTransactions, +} from '../updateTransactions'; +import { + networks, +} from '../../database/prepackaged/networks'; +import { TransactionType } from '../../database/primitives/tables'; +import UtxoApi from '../../../state-fetch/utxoApi'; +import { RustModule } from '../../../cardanoCrypto/rustLoader'; + +jest.mock('../../database/initialSeed'); + +const networkTransactions: number => Array = (purpose) => [ + { + hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545', + height: 218608, + block_hash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba25', + time: '2019-09-13T16:37:16.000Z', + last_update: '2019-09-13T16:37:16.000Z', + tx_state: 'Successful', + tx_ordinal: 0, + epoch: 10, + slot: 3650, + inputs: [ + { + // 'Ae2tdPwUPEZ5PxKxoyZDgjsKgMWMpTRa4PH3sVgARSGBsWwNBH3qg7cMFsP' + address: getSingleAddressString( + ABANDON_SHARE, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.EXTERNAL, + 7 + ] + ), + amount: '4000000', + id: '9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d200', + index: 0, + txHash: '9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d20', + assets: [], + } + ], + outputs: [ + { + // 'Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo' + address: getSingleAddressString( + TX_TEST_MNEMONIC_1, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.EXTERNAL, + 4 + ] + ), + amount: '2100000', + assets: [], + }, + { + // 'Ae2tdPwUPEZE9RAm3d3zuuh22YjqDxhR1JF6G93uJsRrk51QGHzRUzLvDjL' + address: getSingleAddressString( + ABANDON_SHARE, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.INTERNAL, + 12 + ] + ), + amount: '1731391', + assets: [], + } + ], + }, + { + hash: 'hash2', + height: 218607, + block_hash: 'blockhash2', + time: '2019-09-13T16:37:16.000Z', + last_update: '2019-09-13T16:37:16.000Z', + tx_state: 'Successful', + tx_ordinal: 0, + epoch: 10, + slot: 3650, + inputs: [ + { + // 'Ae2tdPwUPEZ5PxKxoyZDgjsKgMWMpTRa4PH3sVgARSGBsWwNBH3qg7cMFsP' + address: getSingleAddressString( + ABANDON_SHARE, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.EXTERNAL, + 7 + ] + ), + amount: '4000000', + id: '9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d200', + index: 0, + txHash: '9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d20', + assets: [], + } + ], + outputs: [ + { + // 'Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo' + address: getSingleAddressString( + TX_TEST_MNEMONIC_1, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.EXTERNAL, + 5 + ] + ), + amount: '2100000', + assets: [], + }, + { + // 'Ae2tdPwUPEZE9RAm3d3zuuh22YjqDxhR1JF6G93uJsRrk51QGHzRUzLvDjL' + address: getSingleAddressString( + ABANDON_SHARE, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.INTERNAL, + 12 + ] + ), + amount: '1731391', + assets: [], + } + ], + }, +]; + +const nextRegularSpend: number => RemoteTransaction = (purpose) => ({ + hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', + height: 218609, + block_hash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba26', + time: '2019-09-13T16:37:36.000Z', + last_update: '2019-09-13T16:37:36.000Z', + tx_state: 'Successful', + tx_ordinal: 0, + epoch: 10, + slot: 3651, + ttl: '99999999', + inputs: [ + { + // 'Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo' + address: getSingleAddressString( + TX_TEST_MNEMONIC_1, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.EXTERNAL, + 4 + ] + ), + amount: '2100000', + id: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed5450', + index: 0, + txHash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545', + assets: [], + } + ], + outputs: [ + { + // 'Ae2tdPwUPEZ3Kt2BJnDMQggxEA4c9MTagByH41rJkv2k82dBch2nqMAdyHJ' + address: getSingleAddressString( + TX_TEST_MNEMONIC_1, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.INTERNAL, + 0 + ] + ), + amount: '1100000', + assets: [], + }, + { + // Ae2tdPwUPEYxsngJhnW49jrmGuaCvQK34Hqrnx5w5SWxgfjDkSDcnrRdT5G + address: getSingleAddressString( + TX_TEST_MNEMONIC_1, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.EXTERNAL, + 19 + ] + ), + amount: '900000', + assets: [], + }, + { + // TODO: find address + address: getSingleAddressString( + TX_TEST_MNEMONIC_1, + [ + WalletTypePurpose.CIP1852, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.EXTERNAL, + 19 + ] + ), + amount: '900000', + assets: [], + } + ], + type: 'shelley', + fee: '100000', + certificates: [], + withdrawals: [{ + address: '619a57f784ef8f9a9d3d25a905e4df27d46843d7a0b93d162cdfae6cdc', + amount: '1000', + }], + metadata: null, +}); + +beforeAll(async () => { + await RustModule.load(); +}); + +beforeEach(() => { + mockDate(); +}); + +async function syncingSimpleTransaction( + purposeForTest: WalletTypePurposeT, +): Promise { + const db = await loadLovefieldDB(schema.DataStoreType.MEMORY); + + const network = networks.CardanoMainnet; + const txHistory = networkTransactions(purposeForTest); + + UtxoApi.utxoApiFactory = (_: string) => new MockUtxoApi(txHistory, 1); + + const publicDeriver = await setup(db, TX_TEST_MNEMONIC_1, purposeForTest); + + const checkAddressesInUse = genCheckAddressesInUse(txHistory, network); + const getTransactionsHistoryForAddresses = genGetTransactionsHistoryForAddresses( + txHistory, + network, + ); + const getBestBlock = genGetBestBlock(txHistory); + const getTokenInfo = genGetTokenInfo(); + const getMultiAssetMetadata = genGetMultiAssetMetadata(); + + const withDisplayCutoff = asDisplayCutoff(publicDeriver); + if (!withDisplayCutoff) throw new Error('missing display cutoff functionality'); + const withUtxoBalance = asGetUtxoBalance(withDisplayCutoff); + if (!withUtxoBalance) throw new Error('missing utxo balance functionality'); + const withUtxos = asGetAllUtxos(withUtxoBalance); + if (!withUtxos) throw new Error('missing get all addresses functionality'); + const basePubDeriver = withUtxos; + + expect(basePubDeriver != null).toEqual(true); + if (basePubDeriver == null) { + throw new Error('basePubDeriver missing a functionality'); + } + + { + await updateTransactions( + db, + basePubDeriver, + checkAddressesInUse, + getTransactionsHistoryForAddresses, + getBestBlock, + getTokenInfo, + getMultiAssetMetadata + ); + + { + const expectedAddressing = [ + purposeForTest, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.EXTERNAL, + 4 + ]; + const response = await basePubDeriver.getAllUtxos(); + expect(response).toEqual([{ + // 'Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo' + address: getSingleAddressString( + TX_TEST_MNEMONIC_1, + expectedAddressing + ), + addressing: { + path: expectedAddressing, + startLevel: 1, + }, + output: { + Transaction: { + Type: TransactionType.CardanoByron, + ErrorMessage: null, + Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545', + Digest: 8.191593645542673e-27, + Ordinal: 0, + BlockId: 1, + LastUpdateTime: 1568392636000, + Status: 1, + TransactionId: 1, + Extra: null, + }, + UtxoTransactionOutput: { + AddressId: 5, + IsUnspent: true, + OutputIndex: 0, + TransactionId: 1, + UtxoTransactionOutputId: 1, + ErgoBoxId: null, + ErgoCreationHeight: null, + ErgoRegisters: null, + ErgoTree: null, + TokenListId: 1, + }, + tokens: [{ + Token: { + Digest: 6.262633522161549e-167, + IsDefault: true, + IsNFT: false, + Identifier: '', + Metadata: { + assetName: '', + longName: null, + numberOfDecimals: 6, + policyId: '', + ticker: 'ADA', + type: 'Cardano', + }, + NetworkId: 0, + TokenId: 1, + }, + TokenList: { + Amount: '2100000', + ListId: 1, + TokenId: 1, + TokenListItemId: 2, + }, + }], + } + }]); + } + + { + const response = await basePubDeriver.getUtxoBalance(); + expect(response.getDefault()).toEqual(new BigNumber('2100000')); + } + + { + const response = await basePubDeriver.getUtxoBalance(); + expect(response.getDefault()).toEqual(new BigNumber('2100000')); + } + + { + const response = await basePubDeriver.getCutoff(); + expect(response).toEqual(4); + } + + { + const response = await publicDeriver.getLastSyncInfo(); + expect(response).toEqual({ + BlockHash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba25', + LastSyncInfoId: 1, + SlotNum: 219650, + Height: 218608, + Time: new Date(0), + }); + } + } + + // test: add a 2nd transaction + { + txHistory.push(nextRegularSpend(purposeForTest)); + + await updateTransactions( + db, + basePubDeriver, + checkAddressesInUse, + getTransactionsHistoryForAddresses, + getBestBlock, + getTokenInfo, + getMultiAssetMetadata + ); + + { + const expectedAddressing1 = [ + purposeForTest, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.INTERNAL, + 0 + ]; + const expectedAddressing2 = [ + purposeForTest, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.EXTERNAL, + 19 + ]; + const response = await basePubDeriver.getAllUtxos(); + expect(response).toEqual([{ + // 'Ae2tdPwUPEZ3Kt2BJnDMQggxEA4c9MTagByH41rJkv2k82dBch2nqMAdyHJ' + address: getSingleAddressString( + TX_TEST_MNEMONIC_1, + expectedAddressing1, + ), + addressing: { + path: expectedAddressing1, + startLevel: 1, + }, + output: { + Transaction: { + Type: TransactionType.CardanoShelley, + ErrorMessage: null, + Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', + Digest: 1.249559827714551e-31, + Ordinal: 0, + BlockId: 2, + LastUpdateTime: 1568392656000, + Status: 1, + TransactionId: 2, + Extra: { + Fee: '100000', + IsValid: true, + Metadata: null, + }, + }, + UtxoTransactionOutput: { + AddressId: 21, + IsUnspent: true, + OutputIndex: 0, + TransactionId: 2, + UtxoTransactionOutputId: 3, + ErgoBoxId: null, + ErgoCreationHeight: null, + ErgoRegisters: null, + ErgoTree: null, + TokenListId: 5, + }, + tokens: [{ + Token: { + Digest: 6.262633522161549e-167, + IsDefault: true, + IsNFT: false, + Identifier: '', + Metadata: { + assetName: '', + longName: null, + numberOfDecimals: 6, + policyId: '', + ticker: 'ADA', + type: 'Cardano', + }, + NetworkId: 0, + TokenId: 1, + }, + TokenList: { + Amount: '1100000', + ListId: 5, + TokenId: 1, + TokenListItemId: 6, + }, + }], + } + }, + { + // Ae2tdPwUPEYxsngJhnW49jrmGuaCvQK34Hqrnx5w5SWxgfjDkSDcnrRdT5G + address: getSingleAddressString( + TX_TEST_MNEMONIC_1, + expectedAddressing2 + ), + addressing: { + path: expectedAddressing2, + startLevel: 1, + }, + output: { + Transaction: { + Type: TransactionType.CardanoShelley, + ErrorMessage: null, + Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', + Digest: 1.249559827714551e-31, + Ordinal: 0, + BlockId: 2, + LastUpdateTime: 1568392656000, + Status: 1, + TransactionId: 2, + Extra: { + Fee: '100000', + IsValid: true, + Metadata: null, + }, + }, + UtxoTransactionOutput: { + AddressId: 20, + IsUnspent: true, + OutputIndex: 1, + TransactionId: 2, + UtxoTransactionOutputId: 4, + ErgoBoxId: null, + ErgoCreationHeight: null, + ErgoRegisters: null, + ErgoTree: null, + TokenListId: 6, + }, + tokens: [{ + Token: { + Digest: 6.262633522161549e-167, + IsDefault: true, + IsNFT: false, + Identifier: '', + Metadata: { + assetName: '', + longName: null, + numberOfDecimals: 6, + policyId: '', + ticker: 'ADA', + type: 'Cardano', + }, + NetworkId: 0, + TokenId: 1, + }, + TokenList: { + Amount: '900000', + ListId: 6, + TokenId: 1, + TokenListItemId: 7, + }, + }], + }, + } + ]); + } + + { + const response = await basePubDeriver.getUtxoBalance(); + expect(response.getDefault()).toEqual(new BigNumber('2000000')); + } + + { + const response = await basePubDeriver.getUtxoBalance(); + expect(response.getDefault()).toEqual(new BigNumber('2000000')); + } + + { + const response = await basePubDeriver.getCutoff(); + expect(response).toEqual(19); + } + + { + const response = await publicDeriver.getLastSyncInfo(); + expect(response).toEqual({ + BlockHash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba26', + LastSyncInfoId: 1, + SlotNum: 219651, + Height: 218609, + Time: new Date(1), + }); + } + } + + const keysForTest = [ + 'Address', + 'Transaction', + 'UtxoTransactionInput', + 'AccountingTransactionInput', + 'UtxoTransactionOutput', + 'LastSyncInfo', + 'Block', + 'Token', + 'TokenList', + ]; + const dump = (await db.export()).tables; + filterDbSnapshot(dump, keysForTest); +} +test('Syncing simple transaction bip44', async (done) => { + await syncingSimpleTransaction(WalletTypePurpose.BIP44); + done(); +}); diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js index 08bf275cdc..5ddbe4b4ee 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js @@ -150,6 +150,8 @@ import { import type { DefaultTokenEntry, } from '../../../../common/lib/MultiToken'; +import { UtxoStorageApi } from '../models/utils'; +import { PublicDeriver } from '../models/PublicDeriver'; type TokensMintMetadata = {| ...{[key: string]: TokenMintMetadata[]} @@ -939,11 +941,17 @@ export async function updateTransactions( .map(key => updateDepTables[key]) .flatMap(table => getAllSchemaTables(db, table)); + const updateUtxoTables = Object + .keys(UtxoStorageApi.depsTables) + .map(key => UtxoStorageApi.depsTables[key]) + .flatMap(table => getAllSchemaTables(db, table)); + await raii( db, [ ...updateTables, ...mapToTables(db, derivationTables), + ...updateUtxoTables, ], async dbTx => { lastSyncInfo = await updateDepTables.GetLastSyncForPublicDeriver.forId( @@ -955,7 +963,8 @@ export async function updateTransactions( GetLastSyncForPublicDeriver, // eslint-disable-line no-unused-vars, no-shadow ...remainingDeps } = updateDepTables; - await rawUpdateTransactions( + + const addresses = await rawUpdateTransactions( db, dbTx, remainingDeps, publicDeriver, @@ -967,6 +976,17 @@ export async function updateTransactions( getTokenInfo, getMultiAssetMetadata ); + + if (addresses) { + if (!(publicDeriver instanceof PublicDeriver)) { + throw new Error('unxpected publicDeriver type'); + } + await updateUtxos( + db, dbTx, + publicDeriver, + addresses, + ); + } } ); } catch (e) { @@ -1185,6 +1205,8 @@ async function rollback( // note: we don't modify the display cutoff since it may confuse the user to suddenly shrink it } +// return all addresses that may have transactions, or undefined if there is no need +// for updating async function rawUpdateTransactions( db: lf$Database, dbTx: lf$Transaction, @@ -1226,7 +1248,7 @@ async function rawUpdateTransactions( derivationTables: Map, getTokenInfo: TokenInfoFunc, getMultiAssetMetadata: MultiAssetMintMetadataFunc, -): Promise { +): Promise | void> { const network = publicDeriver.getParent().getNetworkInfo(); // TODO: consider passing this function in as an argument instead of generating it here const toAbsoluteSlotNumber = await genToAbsoluteSlotNumber( @@ -1246,6 +1268,8 @@ async function rawUpdateTransactions( } } + let requestAddresses = undefined; + if (bestBlock.hash != null) { const untilBlock = bestBlock.hash; @@ -1305,34 +1329,35 @@ async function rawUpdateTransactions( tx: bestInStorage.Transaction.Hash, } }; + requestAddresses = [ + // needs to send legacy addresses directly since they don't use the payment key method + ...addresses.utxoAddresses + .filter(address => address.Type === CoreAddressTypes.CARDANO_LEGACY) + .map(address => address.Hash), + // payment keys will fetch all addresses with the same payment key + ...addresses.utxoAddresses + .filter(address => address.Type === CoreAddressTypes.CARDANO_ENTERPRISE) + .reduce( + (list, next) => { + const wasmAddr = RustModule.WalletV4.Address.from_bytes(Buffer.from(next.Hash, 'hex')); + const enterpriseWasm = RustModule.WalletV4.EnterpriseAddress.from_address(wasmAddr); + if (enterpriseWasm == null) return list; + const keyHash = enterpriseWasm.payment_cred().to_keyhash(); + if (keyHash == null) return list; + list.push(keyHash.to_bech32(Bech32Prefix.PAYMENT_KEY_HASH)); + return list; + }, + [] + ), + // note: sending account addresses is required + // since for example, the staking key registration certificate doesn't need a witness + // so a tx where no input/output belongs to you could register your staking key + ...addresses.accountingAddresses.map(address => address.Hash), + ]; const txsFromNetwork = await getTransactionsHistoryForAddresses({ ...requestKind, network, - addresses: [ - // needs to send legacy addresses directly since they don't use the payment key method - ...addresses.utxoAddresses - .filter(address => address.Type === CoreAddressTypes.CARDANO_LEGACY) - .map(address => address.Hash), - // payment keys will fetch all addresses with the same payment key - ...addresses.utxoAddresses - .filter(address => address.Type === CoreAddressTypes.CARDANO_ENTERPRISE) - .reduce( - (list, next) => { - const wasmAddr = RustModule.WalletV4.Address.from_bytes(Buffer.from(next.Hash, 'hex')); - const enterpriseWasm = RustModule.WalletV4.EnterpriseAddress.from_address(wasmAddr); - if (enterpriseWasm == null) return list; - const keyHash = enterpriseWasm.payment_cred().to_keyhash(); - if (keyHash == null) return list; - list.push(keyHash.to_bech32(Bech32Prefix.PAYMENT_KEY_HASH)); - return list; - }, - [] - ), - // note: sending account addresses is required - // since for example, the staking key registration certificate doesn't need a witness - // so a tx where no input/output belongs to you could register your staking key - ...addresses.accountingAddresses.map(address => address.Hash), - ], + addresses: requestAddresses, untilBlock, }); @@ -1401,6 +1426,8 @@ async function rawUpdateTransactions( Height: bestBlock.height, } ); + + return requestAddresses; } /** @@ -2834,3 +2861,17 @@ async function certificateToDb( } return result; } + +async function updateUtxos( + db: lf$Database, + dbTx: lf$Transaction, + publicDeriver: PublicDeriver<>, + addresses: Array, +) { + const { utxoStorageApi, utxoService, } = publicDeriver; + + utxoStorageApi.setDb(db); + utxoStorageApi.setDbTx(dbTx); + + await utxoService.syncUtxoState(addresses); +} diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js index 7825dcdd8f..659e0715c9 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js @@ -39,6 +39,9 @@ import { GetKeyDerivation, } from '../../database/primitives/api/read'; import { addTraitsForBip44Child, addTraitsForCip1852Child } from './traits'; +import { UtxoService } from '@emurgo/yoroi-lib-core/dist/utxo'; +import { UtxoStorageApi, } from '../utils'; +import UtxoApi from '../../../state-fetch/utxoApi'; /** Snapshot of a PublicDeriver in the database */ export class PublicDeriver<+Parent: ConceptualWallet = ConceptualWallet> @@ -51,7 +54,10 @@ implements IPublicDeriver, IRename, IGetLastSyncInfo { +parent: Parent; derivationId: number; pathToPublic: Array; - + utxoService: UtxoService; + // The UtxoStorage depended on by the above UtxoService. + // Exposed because sometimes we need to directly manipulate it. + utxoStorageApi: UtxoStorageApi; /** * This constructor it will NOT populate functionality from db */ @@ -60,6 +66,15 @@ implements IPublicDeriver, IRename, IGetLastSyncInfo { this.parent = data.parent; this.pathToPublic = data.pathToPublic; this.derivationId = data.derivationId; + + const { BackendService } = this.parent.getNetworkInfo().Backend; + if (!BackendService) { + throw new Error('missing backend service URL'); + } + const utxoApi = new UtxoApi(BackendService); + this.utxoStorageApi = new UtxoStorageApi(this.parent.getConceptualWalletId()); + this.utxoService = new UtxoService(utxoApi, this.utxoStorageApi); + return this; } diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js index 6184d2277d..714211ecd5 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js @@ -14,6 +14,14 @@ import { BigNumber } from 'bignumber.js'; +import type { UtxoStorage } from '@emurgo/yoroi-lib-core/dist/utxo' +import type { + Utxo, + UtxoAtSafePoint, + UtxoDiffToBestBlock +} from '@emurgo/yoroi-lib-core/dist/utxo/models' +import type { Utxo as StorageUtxo } from '../database/utxo/tables'; + import type { IPublicDeriver, UtxoAddressPath, @@ -63,6 +71,12 @@ import type { import { GetUtxoTxOutputsWithTx, } from '../database/transactionModels/utxo/api/read'; +import { + GetUtxoAtSafePoint, GetUtxoDiffToBestBlock, +} from '../database/utxo/api/read'; +import { + ModifyUtxoAtSafePoint, ModifyUtxoDiffToBestBlock, +} from '../database/utxo/api/write'; import { TxStatusCodes, } from '../database/primitives/enums'; import { MultiToken } from '../../../../common/lib/MultiToken'; import type { DefaultTokenEntry } from '../../../../common/lib/MultiToken'; @@ -546,3 +560,151 @@ export function verifyFromBip44Root(request: $ReadOnly<{| throw new Error(`${nameof(verifyFromBip44Root)} incorrect addressing size`); } } + +// convert from storage Utxo type to Yoroi-lib Utxo type +function storageUtxoToYoroiLib(utxo: StorageUtxo): Utxo { + return { + ...utxo, + assets: utxo.assets.map( + asset => ( + { + assetId: asset.assetId, + policyId: asset.policyId, + name: asset.name, + amount: asset.amount, + } + ) + ), + amount: new BigNumber(utxo.amount), + }; +} + +function yoroiLibUtxoToStorage(utxo: Utxo): StorageUtxo { + return { + utxoId: utxo.utxoId, + txHash: utxo.txHash, + txIndex: utxo.txIndex, + receiver: utxo.receiver, + blockNum: utxo.blockNum, + + assets: utxo.assets.map( + asset => ( + { + assetId: asset.assetId, + policyId: asset.policyId, + name: asset.name, + amount: asset.amount, + } + ) + ), + amount: utxo.amount.toString(), + }; +} + + +export class UtxoStorageApi implements UtxoStorage { + static depsTables: {| + ModifyUtxoAtSafePoint: Class, + ModifyUtxoDiffToBestBlock: Class, + GetUtxoAtSafePoint: Class, + GetUtxoDiffToBestBlock: Class, + |} = Object.freeze({ + ModifyUtxoAtSafePoint, ModifyUtxoDiffToBestBlock, + GetUtxoAtSafePoint, GetUtxoDiffToBestBlock, + }); + + conceptualWalletId: number; + db: lf$Database; + dbTx: lf$Transaction; + + constructor(conceptualWalletId: number) { + this.conceptualWalletId = conceptualWalletId; + } + + setDb(db: lf$Database): void { + this.db = db; + } + + setDbTx(dbTx: lf$Transaction): void { + this.dbTx = dbTx; + } + + async getUtxoAtSafePoint(): Promise { + const result = await GetUtxoAtSafePoint.forWallet( + this.db, + this.dbTx, + this.conceptualWalletId, + ); + if (result) { + // convert from storage UtxoAtSafePoint type to Yoroi-lib UtxoAtSafePoint type + return { + lastSafeBlockHash: result.UtxoAtSafePoint.lastSafeBlockHash, + utxos: result.UtxoAtSafePoint.utxos.map(storageUtxoToYoroiLib), + }; + } + return undefined; + } + + async getUtxoDiffToBestBlock(): Promise { + return ( + await GetUtxoDiffToBestBlock.forWallet( + this.db, + this.dbTx, + this.conceptualWalletId, + ) + ).map(utxoDiffToBestBlock => ( + { + ...utxoDiffToBestBlock, + newUtxos: utxoDiffToBestBlock.newUtxos.map(storageUtxoToYoroiLib), + } + )); + } + + async replaceUtxoAtSafePoint(utxos: Utxo[], lastSafeBlockHash: string): Promise { + await ModifyUtxoAtSafePoint.addOrReplace( + this.db, + this.dbTx, + this.conceptualWalletId, + { + lastSafeBlockHash, + utxos: utxos.map(yoroiLibUtxoToStorage), + }, + ); + } + + + async clearUtxoState(): Promise { + await ModifyUtxoAtSafePoint.remove( + this.db, + this.dbTx, + this.conceptualWalletId, + ); + await ModifyUtxoDiffToBestBlock.removeAll( + this.db, + this.dbTx, + this.conceptualWalletId, + ); + } + + async appendUtxoDiffToBestBlock(diff: UtxoDiffToBestBlock): Promise { + await ModifyUtxoDiffToBestBlock.add( + this.db, + this.dbTx, + this.conceptualWalletId, + { + lastBestBlockHash: diff.lastBestBlockHash, + spentUtxoIds: diff.spentUtxoIds, + newUtxos: diff.newUtxos.map(yoroiLibUtxoToStorage), + }, + ); + } + + async removeDiffWithBestBlock(blockHash: string): Promise { + await ModifyUtxoDiffToBestBlock.remove( + this.db, + this.dbTx, + this.conceptualWalletId, + blockHash, + ); + } +} From 662bb167968e10cde15c5b4646c2523ea2c75e5e Mon Sep 17 00:00:00 2001 From: yushi Date: Wed, 25 May 2022 23:40:59 +0800 Subject: [PATCH 008/199] use Yoroi-lib UtxoService result when getting UTXOs --- .../api/ada/lib/state-fetch/mockNetwork.js | 87 ++-- .../ada/lib/storage/bridge/tests/utxo.test.js | 469 ++++++++---------- .../database/transactionModels/utxo/tables.js | 2 +- .../lib/storage/models/PublicDeriver/index.js | 3 + .../models/PublicDeriver/interfaces.js | 40 +- .../storage/models/PublicDeriver/traits.js | 103 +++- .../app/api/ada/lib/storage/models/utils.js | 14 +- 7 files changed, 390 insertions(+), 328 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js index ea3e7ef2be..ee2fcea27b 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js @@ -704,17 +704,18 @@ export class MockUtxoApi implements UtxoApiContract { ) { this.blockchain = blockchain; - let blockCount = 0; - let currentBlockHash = blockchain[blockchain.length - 1].block_hash; + let lastHeight = blockchain[blockchain.length - 1].height; + if (lastHeight == null) { + throw new Error('missing height'); + } let i; for (i = blockchain.length - 1; i >= 0; i --) { - if (blockchain[i].block_hash !== currentBlockHash) { - blockCount += 1; - if (blockCount = safeConfirmations) { - break; - } else { - currentBlockHash = blockchain[i].block_hash; - } + const currentHeight = blockchain[i].height; + if (currentHeight == null) { + throw new Error('missing height'); + } + if (lastHeight - currentHeight >= safeConfirmations) { + break; } } if (i === -1) { @@ -743,39 +744,30 @@ export class MockUtxoApi implements UtxoApiContract { async getTipStatusWithReference( bestBlocks: string[] ): Promise> { - for (let i = this.blockchain.length - 1; i >= 0; i--) { - const hash = this.blockchain[i].block_hash; - if (!hash) { - throw new Error('expect hash'); - } - const height = this.blockchain[i].height; - if (height == null) { - throw new Error('expect height'); - } - if (bestBlocks.includes(hash)) { - const safeBlockHash = this.blockchain[this.lastSafeBlockTxIndex].block_hash; - if (!safeBlockHash) { - throw new Error('expect hash'); - } - const safeBlockHeight = this.blockchain[this.lastSafeBlockTxIndex].height; - if (safeBlockHeight == null) { - throw new Error('expect block height'); + const blocks = bestBlocks.map( + hash => { + const index = this.blockchain.findIndex(tx => tx.block_hash === hash); + if (index === -1) { + return { index, height: null, hash }; } - return { - result: UtxoApiResult.SUCCESS, - value: { - reference: { - lastFoundBestBlock: hash, - lastFoundSafeBlock: (safeBlockHeight >= height) - ? safeBlockHash - : hash, - } - } - }; + const height = this.blockchain[index].height; + return { index, height, hash }; } + ).filter(b => b.index !== -1).sort((b1, b2) => b1.index - b2.index); + if (blocks.length === 0) { + return { + result: UtxoApiResult.SAFEBLOCK_ROLLBACK + }; } + return { - result: UtxoApiResult.SAFEBLOCK_ROLLBACK + result: UtxoApiResult.SUCCESS, + value: { + reference: { + lastFoundBestBlock: blocks[blocks.length - 1].hash, + lastFoundSafeBlock: blocks[0].hash, + } + } }; } @@ -832,19 +824,20 @@ export class MockUtxoApi implements UtxoApiContract { async getUtxoDiffSincePoint(req: UtxoDiffSincePointRequest): Promise> { const { addresses, untilBlockHash, afterBestBlock, } = req; - let seenBestBlock = false; + let seenUntilBlock = false; let utxoDiffItems = []; - for (let i = 0; i <= this.blockchain.length; i++) { + for (let i = this.blockchain.length - 1; i >= 0; i--) { const tx = this.blockchain[i]; - if (seenBestBlock) { + if (tx.block_hash === untilBlockHash) { + seenUntilBlock = true; + } + + if (seenUntilBlock) { if (tx.block_hash === afterBestBlock) { - continue; - } - if (tx.block_hash === untilBlockHash) { - seenUntilBlock = true; break; } + tx.outputs.filter( ({ address }) => addresses.includes(address) ).forEach((output, outputIndex) => { @@ -881,10 +874,6 @@ export class MockUtxoApi implements UtxoApiContract { }: UtxoDiffItem) ); }); - } else { - if (tx.block_hash === afterBestBlock) { - seenBestBlock = true; - } } } if (!seenUntilBlock) { diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js index 123986c6b6..688b602ecc 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js @@ -117,7 +117,7 @@ const networkTransactions: number => Array = (purpose) => [ }, { hash: 'hash2', - height: 218607, + height: 218609, block_hash: 'blockhash2', time: '2019-09-13T16:37:16.000Z', last_update: '2019-09-13T16:37:16.000Z', @@ -139,9 +139,9 @@ const networkTransactions: number => Array = (purpose) => [ ] ), amount: '4000000', - id: '9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d200', + id: 'txHash10', index: 0, - txHash: '9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d20', + txHash: 'txHash1', assets: [], } ], @@ -181,9 +181,9 @@ const networkTransactions: number => Array = (purpose) => [ ]; const nextRegularSpend: number => RemoteTransaction = (purpose) => ({ - hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', - height: 218609, - block_hash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba26', + hash: 'hash3', + height: 218610, + block_hash: 'blockhash3', time: '2019-09-13T16:37:36.000Z', last_update: '2019-09-13T16:37:36.000Z', tx_state: 'Successful', @@ -242,21 +242,6 @@ const nextRegularSpend: number => RemoteTransaction = (purpose) => ({ amount: '900000', assets: [], }, - { - // TODO: find address - address: getSingleAddressString( - TX_TEST_MNEMONIC_1, - [ - WalletTypePurpose.CIP1852, - CoinTypes.CARDANO, - 0 + HARD_DERIVATION_START, - ChainDerivations.EXTERNAL, - 19 - ] - ), - amount: '900000', - assets: [], - } ], type: 'shelley', fee: '100000', @@ -322,101 +307,100 @@ async function syncingSimpleTransaction( ); { - const expectedAddressing = [ - purposeForTest, - CoinTypes.CARDANO, - 0 + HARD_DERIVATION_START, - ChainDerivations.EXTERNAL, - 4 - ]; const response = await basePubDeriver.getAllUtxos(); - expect(response).toEqual([{ - // 'Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo' - address: getSingleAddressString( - TX_TEST_MNEMONIC_1, - expectedAddressing - ), - addressing: { - path: expectedAddressing, - startLevel: 1, - }, - output: { - Transaction: { - Type: TransactionType.CardanoByron, - ErrorMessage: null, - Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545', - Digest: 8.191593645542673e-27, - Ordinal: 0, - BlockId: 1, - LastUpdateTime: 1568392636000, - Status: 1, - TransactionId: 1, - Extra: null, - }, - UtxoTransactionOutput: { - AddressId: 5, - IsUnspent: true, - OutputIndex: 0, - TransactionId: 1, - UtxoTransactionOutputId: 1, - ErgoBoxId: null, - ErgoCreationHeight: null, - ErgoRegisters: null, - ErgoTree: null, - TokenListId: 1, + + expect(response).toEqual( + [ + { + output: { + Transaction: { + Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545' + }, + UtxoTransactionOutput: { + OutputIndex: 0, + ErgoBoxId: null, + ErgoCreationHeight: null, + ErgoTree: null, + ErgoRegisters: null, + }, + tokens: [ + { + Token: { + Digest: 6.262633522161549e-167, + NetworkId: 0, + Identifier: '', + IsDefault: true, + IsNFT: false, + Metadata: { + type: 'Cardano', + policyId: '', + assetName: '', + ticker: 'ADA', + longName: null, + numberOfDecimals: 6 + }, + TokenId: 1 + }, + TokenList: { Amount: '2100000' } + } + ] + }, + addressing: { + path: [ 2147483692, 2147485463, 2147483648, 0, 4 ], + startLevel: 1 + }, + address: 'Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo' }, - tokens: [{ - Token: { - Digest: 6.262633522161549e-167, - IsDefault: true, - IsNFT: false, - Identifier: '', - Metadata: { - assetName: '', - longName: null, - numberOfDecimals: 6, - policyId: '', - ticker: 'ADA', - type: 'Cardano', + { + output: { + Transaction: { Hash: 'hash2' }, + UtxoTransactionOutput: { + OutputIndex: 0, + ErgoBoxId: null, + ErgoCreationHeight: null, + ErgoTree: null, + ErgoRegisters: null, }, - NetworkId: 0, - TokenId: 1, + tokens: [ + { + Token: { + Digest: 6.262633522161549e-167, + NetworkId: 0, + Identifier: '', + IsDefault: true, + IsNFT: false, + Metadata: { + type: 'Cardano', + policyId: '', + assetName: '', + ticker: 'ADA', + longName: null, + numberOfDecimals: 6 + }, + TokenId: 1 + }, + TokenList: { Amount: '2100000' } + } + ] }, - TokenList: { - Amount: '2100000', - ListId: 1, - TokenId: 1, - TokenListItemId: 2, + addressing: { + path: [ 2147483692, 2147485463, 2147483648, 0, 5 ], + startLevel: 1 }, - }], - } - }]); - } - - { - const response = await basePubDeriver.getUtxoBalance(); - expect(response.getDefault()).toEqual(new BigNumber('2100000')); + address: 'Ae2tdPwUPEYxzZH7sSyyXK6DDmjCxRajXUXFqbEjtxfPN7HZzQfXr4hxKwT' + } + ] + ); } { const response = await basePubDeriver.getUtxoBalance(); - expect(response.getDefault()).toEqual(new BigNumber('2100000')); + expect(response.getDefault()).toEqual(new BigNumber('4200000')); } { const response = await basePubDeriver.getCutoff(); - expect(response).toEqual(4); - } - - { - const response = await publicDeriver.getLastSyncInfo(); - expect(response).toEqual({ - BlockHash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba25', - LastSyncInfoId: 1, - SlotNum: 219650, - Height: 218608, - Time: new Date(0), - }); + expect(response).toEqual(5); } } @@ -433,164 +417,131 @@ async function syncingSimpleTransaction( getTokenInfo, getMultiAssetMetadata ); - { - const expectedAddressing1 = [ - purposeForTest, - CoinTypes.CARDANO, - 0 + HARD_DERIVATION_START, - ChainDerivations.INTERNAL, - 0 - ]; - const expectedAddressing2 = [ - purposeForTest, - CoinTypes.CARDANO, - 0 + HARD_DERIVATION_START, - ChainDerivations.EXTERNAL, - 19 - ]; const response = await basePubDeriver.getAllUtxos(); - expect(response).toEqual([{ - // 'Ae2tdPwUPEZ3Kt2BJnDMQggxEA4c9MTagByH41rJkv2k82dBch2nqMAdyHJ' - address: getSingleAddressString( - TX_TEST_MNEMONIC_1, - expectedAddressing1, - ), - addressing: { - path: expectedAddressing1, - startLevel: 1, - }, - output: { - Transaction: { - Type: TransactionType.CardanoShelley, - ErrorMessage: null, - Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', - Digest: 1.249559827714551e-31, - Ordinal: 0, - BlockId: 2, - LastUpdateTime: 1568392656000, - Status: 1, - TransactionId: 2, - Extra: { - Fee: '100000', - IsValid: true, - Metadata: null, + + expect(response).toEqual( + [ + { + output: { + Transaction: { Hash: 'hash2' }, + UtxoTransactionOutput: { + OutputIndex: 0, + ErgoBoxId: null, + ErgoCreationHeight: null, + ErgoTree: null, + ErgoRegisters: null + }, + tokens: [ + { + Token: { + Digest: 6.262633522161549e-167, + NetworkId: 0, + Identifier: '', + IsDefault: true, + IsNFT: false, + Metadata: { + type: 'Cardano', + policyId: '', + assetName: '', + ticker: 'ADA', + longName: null, + numberOfDecimals: 6 + }, + TokenId: 1 + }, + TokenList: { Amount: '2100000' } + } + ] }, + addressing: { + path: [ 2147483692, 2147485463, 2147483648, 0, 5 ], + startLevel: 1 + }, + address: 'Ae2tdPwUPEYxzZH7sSyyXK6DDmjCxRajXUXFqbEjtxfPN7HZzQfXr4hxKwT' }, - UtxoTransactionOutput: { - AddressId: 21, - IsUnspent: true, - OutputIndex: 0, - TransactionId: 2, - UtxoTransactionOutputId: 3, - ErgoBoxId: null, - ErgoCreationHeight: null, - ErgoRegisters: null, - ErgoTree: null, - TokenListId: 5, - }, - tokens: [{ - Token: { - Digest: 6.262633522161549e-167, - IsDefault: true, - IsNFT: false, - Identifier: '', - Metadata: { - assetName: '', - longName: null, - numberOfDecimals: 6, - policyId: '', - ticker: 'ADA', - type: 'Cardano', + { + output: { + Transaction: { Hash: 'hash3' }, + UtxoTransactionOutput: { + OutputIndex: 0, + ErgoBoxId: null, + ErgoCreationHeight: null, + ErgoTree: null, + ErgoRegisters: null }, - NetworkId: 0, - TokenId: 1, - }, - TokenList: { - Amount: '1100000', - ListId: 5, - TokenId: 1, - TokenListItemId: 6, + tokens: [ + { + Token: { + Digest: 6.262633522161549e-167, + NetworkId: 0, + Identifier: '', + IsDefault: true, + IsNFT: false, + Metadata: { + type: 'Cardano', + policyId: '', + assetName: '', + ticker: 'ADA', + longName: null, + numberOfDecimals: 6 + }, + TokenId: 1 + }, + TokenList: { Amount: '1100000' } + } + ] }, - }], - } - }, - { - // Ae2tdPwUPEYxsngJhnW49jrmGuaCvQK34Hqrnx5w5SWxgfjDkSDcnrRdT5G - address: getSingleAddressString( - TX_TEST_MNEMONIC_1, - expectedAddressing2 - ), - addressing: { - path: expectedAddressing2, - startLevel: 1, - }, - output: { - Transaction: { - Type: TransactionType.CardanoShelley, - ErrorMessage: null, - Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', - Digest: 1.249559827714551e-31, - Ordinal: 0, - BlockId: 2, - LastUpdateTime: 1568392656000, - Status: 1, - TransactionId: 2, - Extra: { - Fee: '100000', - IsValid: true, - Metadata: null, + addressing: { + path: [ 2147483692, 2147485463, 2147483648, 1, 0 ], + startLevel: 1 }, + address: 'Ae2tdPwUPEZ3Kt2BJnDMQggxEA4c9MTagByH41rJkv2k82dBch2nqMAdyHJ' }, - UtxoTransactionOutput: { - AddressId: 20, - IsUnspent: true, - OutputIndex: 1, - TransactionId: 2, - UtxoTransactionOutputId: 4, - ErgoBoxId: null, - ErgoCreationHeight: null, - ErgoRegisters: null, - ErgoTree: null, - TokenListId: 6, - }, - tokens: [{ - Token: { - Digest: 6.262633522161549e-167, - IsDefault: true, - IsNFT: false, - Identifier: '', - Metadata: { - assetName: '', - longName: null, - numberOfDecimals: 6, - policyId: '', - ticker: 'ADA', - type: 'Cardano', + { + output: { + Transaction: { Hash: 'hash3' }, + UtxoTransactionOutput: { + OutputIndex: 1, + ErgoBoxId: null, + ErgoCreationHeight: null, + ErgoTree: null, + ErgoRegisters: null }, - NetworkId: 0, - TokenId: 1, + tokens: [ + { + Token: { + Digest: 6.262633522161549e-167, + NetworkId: 0, + Identifier: '', + IsDefault: true, + IsNFT: false, + Metadata: { + type: 'Cardano', + policyId: '', + assetName: '', + ticker: 'ADA', + longName: null, + numberOfDecimals: 6 + }, + TokenId: 1 + }, + TokenList: { Amount: '900000' } + } + ] }, - TokenList: { - Amount: '900000', - ListId: 6, - TokenId: 1, - TokenListItemId: 7, + addressing: { + path: [ 2147483692, 2147485463, 2147483648, 0, 19 ], + startLevel: 1 }, - }], - }, - } - ]); - } - - { - const response = await basePubDeriver.getUtxoBalance(); - expect(response.getDefault()).toEqual(new BigNumber('2000000')); + address: 'Ae2tdPwUPEYxsngJhnW49jrmGuaCvQK34Hqrnx5w5SWxgfjDkSDcnrRdT5G' + } + ] + ); } - { const response = await basePubDeriver.getUtxoBalance(); - expect(response.getDefault()).toEqual(new BigNumber('2000000')); + expect(response.getDefault()).toEqual(new BigNumber('4100000')); } { @@ -598,31 +549,7 @@ async function syncingSimpleTransaction( expect(response).toEqual(19); } - { - const response = await publicDeriver.getLastSyncInfo(); - expect(response).toEqual({ - BlockHash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba26', - LastSyncInfoId: 1, - SlotNum: 219651, - Height: 218609, - Time: new Date(1), - }); - } } - - const keysForTest = [ - 'Address', - 'Transaction', - 'UtxoTransactionInput', - 'AccountingTransactionInput', - 'UtxoTransactionOutput', - 'LastSyncInfo', - 'Block', - 'Token', - 'TokenList', - ]; - const dump = (await db.export()).tables; - filterDbSnapshot(dump, keysForTest); } test('Syncing simple transaction bip44', async (done) => { await syncingSimpleTransaction(WalletTypePurpose.BIP44); diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/transactionModels/utxo/tables.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/transactionModels/utxo/tables.js index 4c6789fe8d..0415bb7651 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/transactionModels/utxo/tables.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/transactionModels/utxo/tables.js @@ -40,7 +40,7 @@ export const UtxoTransactionInputSchema: {| } }; -type ErgoFields = {| +export type ErgoFields = {| ErgoBoxId: string, ErgoCreationHeight: number, ErgoTree: string, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js index 659e0715c9..11ccf7b03a 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js @@ -185,6 +185,9 @@ implements IPublicDeriver, IRename, IGetLastSyncInfo { async tx => this.rawGetLastSyncInfo(tx, deps, body) ); } + + getUtxoService: void => UtxoService = () => this.utxoService; + getUtxoStorageApi: void => UtxoStorageApi = () => this.utxoStorageApi; } export async function refreshPublicDeriverFunctionality( diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js index cdad359c6d..c9e76daf4d 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js @@ -11,6 +11,15 @@ import { GetUtxoTxOutputsWithTx, } from '../../database/transactionModels/utxo/api/read'; import type { UtxoTxOutput } from '../../database/transactionModels/utxo/api/read'; +import type { + UtxoTransactionOutputRow, + ErgoFields, +} from '../../database/transactionModels/utxo/tables'; +import type { + TransactionRow, + TokenRow, + TokenListRow, +} from '../../database/primitives/tables'; import type { AddressRow, @@ -60,6 +69,14 @@ import type { } from '../../database/walletTypes/common/utils'; import type { Bip44ChainInsert } from '../../database/walletTypes/common/tables'; import { MultiToken } from '../../../../../common/lib/MultiToken'; +import type { + GetUtxoAtSafePoint, + GetUtxoDiffToBestBlock, +} from '../../database/utxo/api/read'; +import { GetToken } from '../../database/primitives/api/read'; +import { UtxoService } from '@emurgo/yoroi-lib-core/dist/utxo'; +import { UtxoStorageApi, } from '../utils'; + export type Address = {| +address: string, @@ -99,6 +116,8 @@ export interface IPublicDeriver<+Parent: ConceptualWallet = ConceptualWallet> { getPathToPublic(): Array; getDerivationId(): number; getFullPublicDeriverInfo(): Promise<$ReadOnly>; + getUtxoService: void => UtxoService; + getUtxoStorageApi: void => UtxoStorageApi; } export type PathRequest = void; @@ -137,6 +156,19 @@ export interface IGetPublic { +changePubDeriverPassword: IChangePasswordRequestFunc, } +type GetAllUtxosOutput = {| + Transaction: { +Hash: string, ... }, + UtxoTransactionOutput: $ReadOnly<{ + OutputIndex: number, + ...WithNullableFields, + ... + }>, + +tokens: $ReadOnlyArray<$ReadOnly<{| + +TokenList: $ReadOnly<{ +Amount: string, ... }>, + +Token: $ReadOnly, + |}>> +|}; + export type IGetAllUtxoAddressesRequest = PathRequest; export type IGetAllUtxoAddressesResponse = Array; export type IGetAllUtxoAddressesFunc = ( @@ -144,7 +176,7 @@ export type IGetAllUtxoAddressesFunc = ( ) => Promise; export type IGetAllUtxosRequest = void; export type IGetAllUtxosResponse = Array<{| - output: $ReadOnly; + output: $ReadOnly; ...Addressing, ...Address, |}>; @@ -158,6 +190,9 @@ export interface IGetAllUtxos { GetPathWithSpecific: Class, GetAddress: Class, GetUtxoTxOutputsWithTx: Class, + GetUtxoAtSafePoint: Class, + GetUtxoDiffToBestBlock: Class, + GetToken: Class, GetDerivationSpecific: Class, |}, IGetAllUtxosRequest @@ -287,6 +322,9 @@ export interface IGetUtxoBalance { GetPathWithSpecific: Class, GetAddress: Class, GetUtxoTxOutputsWithTx: Class, + GetUtxoAtSafePoint: Class, + GetUtxoDiffToBestBlock: Class, + GetToken: Class, GetDerivationSpecific: Class, |}, IGetUtxoBalanceRequest diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js index 3cf4430e1b..4e06d82ead 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js @@ -101,9 +101,14 @@ import { GetKeyDerivation, GetKey, GetAddress, + GetToken, } from '../../database/primitives/api/read'; import { CoreAddressTypes } from '../../database/primitives/enums'; -import type { KeyRow, KeyDerivationRow, } from '../../database/primitives/tables'; +import type { + KeyRow, + KeyDerivationRow, + TokenRow, +} from '../../database/primitives/tables'; import { ModifyKey, ModifyAddress, } from '../../database/primitives/api/write'; import { v2genAddressBatchFunc, } from '../../../../restoration/byron/scan'; @@ -131,6 +136,15 @@ import { isJormungandr, } from '../../database/prepackaged/networks'; import { BIP32PublicKey, deriveKey } from '../../../../../common/lib/crypto/keys/keyRepository'; +import { + GetUtxoAtSafePoint, + GetUtxoDiffToBestBlock, +} from '../../database/utxo/api/read'; +import { PublicDeriver } from './'; +import type { + Utxo, +} from '@emurgo/yoroi-lib-core/dist/utxo/models'; +import { isErgo } from '../../database/prepackaged/networks'; interface Empty {} type HasPrivateDeriverDependencies = IPublicDeriver; @@ -202,6 +216,9 @@ const GetAllUtxosMixin = ( GetPathWithSpecific: Class, GetAddress: Class, GetUtxoTxOutputsWithTx: Class, + GetUtxoAtSafePoint: Class, + GetUtxoDiffToBestBlock: Class, + GetToken: Class, GetDerivationSpecific: Class, |}, IGetAllUtxosRequest, @@ -222,12 +239,79 @@ const GetAllUtxosMixin = ( undefined, derivationTables, ); + // TODO: perhaps should use seperate types for Ergo and Cardano wallets instead + // of condition + if (!isErgo(this.getParent().getNetworkInfo())) { + const utxoStorageApi = this.getUtxoStorageApi(); + utxoStorageApi.setDb(super.getDb()); + utxoStorageApi.setDbTx(tx); + const utxosInStorage: Array = await this.getUtxoService().getAvailableUtxos(); + + const networkId = this.getParent().getNetworkInfo().NetworkId; + const tokens = (await deps.GetToken.fromIdentifier( + super.getDb(), tx, + [ + '', + ...utxosInStorage.flatMap( + ({ assets }) => assets.map(asset => asset.assetId) + ) + ] + )).filter(token => token.NetworkId === networkId); + const tokenMap = new Map>( + tokens.map(token => [ token.Identifier, token ]) + ); + + const addressingMap = new Map( + addresses.flatMap(family => family.addrs.map(addr => [addr.Hash, { + addressing: family.addressing, + address: addr.Hash, + }])) + ); + + const addressedUtxos = utxosInStorage.map(utxo => { + const addressingInfo = addressingMap.get(utxo.receiver); + if (addressingInfo == null) { + throw new Error(`${nameof(GetAllUtxos)}::${nameof(this.rawGetAllUtxos)} should never happen`); + } + + const tokens = [ '', ...utxo.assets.map(asset => asset.assetId) ].map((tokenId, i) => { + let amount; + if (i === 0) { + amount = utxo.amount; + } else { + amount = utxo.assets[i].amount; + } + const token = tokenMap.get(tokenId); + if (!token) { + throw new Error(`missing token ID in UTXO: ${tokenId}`); + } + return { Token: token, TokenList: { Amount: amount.toString() } }; + }); + return { + output: { + Transaction: { Hash: utxo.txHash }, + UtxoTransactionOutput: { + OutputIndex: utxo.txIndex, + ErgoBoxId: null, + ErgoCreationHeight: null, + ErgoTree: null, + ErgoRegisters: null, + }, + tokens, + }, + addressing: addressingInfo.addressing, + address: addressingInfo.address, + }; + }); + } + // Ergo: const addressIds = addresses.flatMap(family => family.addrs.map(addr => addr.AddressId)); const utxosInStorage = await deps.GetUtxoTxOutputsWithTx.getUtxo( super.getDb(), tx, addressIds, this.getParent().getNetworkInfo().NetworkId ); + const addressingMap = new Map( addresses.flatMap(family => family.addrs.map(addr => [addr.AddressId, { addressing: family.addressing, @@ -246,6 +330,7 @@ const GetAllUtxosMixin = ( address: addressingInfo.address, }); } + return addressedUtxos; } getAllUtxos: IGetAllUtxosRequest => Promise = async ( @@ -256,6 +341,9 @@ const GetAllUtxosMixin = ( GetPathWithSpecific, GetAddress, GetUtxoTxOutputsWithTx, + GetUtxoAtSafePoint, + GetUtxoDiffToBestBlock, + GetToken, GetDerivationSpecific, }); const depTables = Object @@ -2350,18 +2438,24 @@ const GetUtxoBalanceMixin = ( {| GetPathWithSpecific: Class, GetAddress: Class, - GetUtxoTxOutputsWithTx: Class, + GetUtxoTxOutputsWithTx: Class, + GetUtxoAtSafePoint: Class, + GetUtxoDiffToBestBlock: Class, + GetToken: Class, GetDerivationSpecific: Class, |}, IGetUtxoBalanceRequest, Map, ) => Promise = async (tx, deps, _body, derivationTables) => { - const utxos = await this.rawGetAllUtxos( + const utxos: IGetAllUtxosResponse = await this.rawGetAllUtxos( tx, { GetAddress: deps.GetAddress, GetPathWithSpecific: deps.GetPathWithSpecific, GetUtxoTxOutputsWithTx: deps.GetUtxoTxOutputsWithTx, + GetUtxoAtSafePoint: deps.GetUtxoAtSafePoint, + GetUtxoDiffToBestBlock: deps.GetUtxoDiffToBestBlock, + GetToken: deps.GetToken, GetDerivationSpecific: deps.GetDerivationSpecific, }, undefined, @@ -2378,6 +2472,9 @@ const GetUtxoBalanceMixin = ( GetPathWithSpecific, GetAddress, GetUtxoTxOutputsWithTx, + GetUtxoAtSafePoint, + GetUtxoDiffToBestBlock, + GetToken, GetDerivationSpecific, }); const depTables = Object diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js index 714211ecd5..b9129e10dc 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js @@ -14,12 +14,12 @@ import { BigNumber } from 'bignumber.js'; -import type { UtxoStorage } from '@emurgo/yoroi-lib-core/dist/utxo' +import type { UtxoStorage } from '@emurgo/yoroi-lib-core/dist/utxo'; import type { Utxo, UtxoAtSafePoint, UtxoDiffToBestBlock -} from '@emurgo/yoroi-lib-core/dist/utxo/models' +} from '@emurgo/yoroi-lib-core/dist/utxo/models'; import type { Utxo as StorageUtxo } from '../database/utxo/tables'; import type { @@ -452,8 +452,16 @@ export function getTokenCountForUtxos( }; } +type UtxoTokenInfo = { + tokens: $ReadOnlyArray<$ReadOnly<{ + TokenList: $ReadOnly<{ Amount: string, ... }>, + Token: $ReadOnly<{ Identifier: string, NetworkId: number, ... }>, + ... + }>>, + ... +}; export function getBalanceForUtxos( - utxos: $ReadOnlyArray<$ReadOnly>, + utxos: $ReadOnlyArray<$ReadOnly>, defaultToken: DefaultTokenEntry, ): IGetUtxoBalanceResponse { const tokens = new MultiToken([], defaultToken); From 9ef56e16c703e5ef0249d9b6ac5b3dc0dc13c45e Mon Sep 17 00:00:00 2001 From: yushi Date: Thu, 26 May 2022 17:48:16 +0800 Subject: [PATCH 009/199] add migration to populate new UTXO storage --- .../app/api/ada/lib/storage/adaMigration.js | 115 ++++++++++++++++++ .../models/PublicDeriver/interfaces.js | 1 + .../storage/models/PublicDeriver/traits.js | 83 +++++++++++-- 3 files changed, 188 insertions(+), 11 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js b/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js index e41ea1c1d6..99aa6d848a 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js @@ -27,6 +27,7 @@ import { removeAllTransactions } from './bridge/updateTransactions'; import { removePublicDeriver } from './bridge/walletBuilder/remove'; import { asHasLevels, + asGetAllUtxos, } from './models/PublicDeriver/traits'; import { ConceptualWallet, @@ -41,6 +42,13 @@ import { import { isCardanoHaskell, isErgo, networks } from './database/prepackaged/networks'; +import { + getAllSchemaTables, + raii, +} from './database/utils'; +import type { BlockRow } from './database/primitives/tables'; +import { GetBlock } from './database/primitives/api/read'; +import { ModifyUtxoAtSafePoint } from './database/utxo/api/write'; export async function migrateToLatest( localStorageApi: LocalStorageApi, @@ -368,5 +376,112 @@ export async function ergoTxHistoryReset( export async function populateNewUtxodata( persistentDb: lf$Database, ): Promise { + const wallets = await loadWalletsFromStorage(persistentDb); + if (wallets.length === 0) { + return false; + } + + for (const publicDeriver of wallets) { + try { + const withLevels = asHasLevels(publicDeriver); + if (isErgo(publicDeriver.getParent().getNetworkInfo())) { + continue; + } + + const withGetAllUtxos = asGetAllUtxos(publicDeriver); + if (!withGetAllUtxos) { + throw new Error('unexpected missing trait'); + } + + const lastSyncInfo = await publicDeriver.getLastSyncInfo(); + const utxos = await withGetAllUtxos.getAllUtxosFromOldDb(); + + const blockIds = utxos.map(utxo => { + // We are using the old getAllUtxos, it does have the BlockId field + // $FlowFixMe[prop-missing] + const blockId = utxo.output.Transaction.BlockId; + if (blockId == null) { + throw new Error('expect transaction to have block ID'); + } + return blockId; + }); + + const db = publicDeriver.getDb(); + const blocks = await raii<$ReadOnlyArray<$ReadOnly>>( + db, + getAllSchemaTables(db, GetBlock), + tx => GetBlock.byIds(db, tx, blockIds) + ); + // block ID => block height + const blockMap = new Map( + blocks.map(block => [block.BlockId, block.Height]) + ); + const newUtxos = utxos.map(utxo => { + const txIndex = utxo.output.UtxoTransactionOutput.OutputIndex; + const txHash = utxo.output.Transaction.Hash; + const defaultTokenId = ''; + const isDefaultToken = token => token.Token.Identifier === defaultTokenId; + const defaultToken = utxo.output.tokens.find(isDefaultToken); + const assets = utxo.output.tokens + .filter(token => !isDefaultToken(token)) + .map(token => { + const { Metadata } = token.Token; + if (Metadata.type !== 'Cardano') { + throw new Error('unexpected token metadata type'); + } + return { + assetId: token.Token.Identifier, + policyId: Metadata.policyId, + name: Metadata.assetName, + amount: token.TokenList.Amount, + } + }); + // We are using the old getAllUtxos, it does have the BlockId field + // $FlowFixMe[prop-missing] + const blockId = utxo.output.Transaction.BlockId; + if (blockId == null) { + throw new Error('expect transaction to have block ID'); + } + const blockNum = blockMap.get(blockId); + if (blockNum == null) { + throw new Error(`can't find block info for ${blockId}`); + } + if (defaultToken == null) { + throw new Error(`missing default token`); + } + return { + utxoId: `${txHash}${txIndex}`, + txHash, + txIndex, + receiver: utxo.address, + amount: defaultToken.TokenList.Amount, + assets, + blockNum + }; + }); + const blockHash = lastSyncInfo.BlockHash; + if (blockHash == null) { + throw new Error('missing block hash'); + } + await raii( + db, + getAllSchemaTables(db, ModifyUtxoAtSafePoint), + tx => ModifyUtxoAtSafePoint.addOrReplace( + db, + tx, + publicDeriver.getParent().getConceptualWalletId(), + { + lastSafeBlockHash: blockHash, + utxos: newUtxos + } + ) + ); + } catch(error) { + Logger.warn(`UTXO storage migration failed: ${error}`); + // It's OK to leave the UTXO storage empty, as Yoroi-lib UtxoService will + // sync from the beginning + } + } + return true; } diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js index c9e76daf4d..3245950e46 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js @@ -198,6 +198,7 @@ export interface IGetAllUtxos { IGetAllUtxosRequest >; +getAllUtxos: IGetAllUtxosFunc; + +getAllUtxosFromOldDb: IGetAllUtxosFunc; +rawGetAllUtxoAddresses: RawTableVariation< IGetAllUtxoAddressesFunc, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js index 4e06d82ead..46c0998b90 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js @@ -229,19 +229,20 @@ const GetAllUtxosMixin = ( _body, derivationTables, ) => { - const addresses = await this.rawGetAllUtxoAddresses( - tx, - { - GetAddress: deps.GetAddress, - GetPathWithSpecific: deps.GetPathWithSpecific, - GetDerivationSpecific: deps.GetDerivationSpecific, - }, - undefined, - derivationTables, - ); // TODO: perhaps should use seperate types for Ergo and Cardano wallets instead - // of condition + // of branching if (!isErgo(this.getParent().getNetworkInfo())) { + const addresses = await this.rawGetAllUtxoAddresses( + tx, + { + GetAddress: deps.GetAddress, + GetPathWithSpecific: deps.GetPathWithSpecific, + GetDerivationSpecific: deps.GetDerivationSpecific, + }, + undefined, + derivationTables, + ); + const utxoStorageApi = this.getUtxoStorageApi(); utxoStorageApi.setDb(super.getDb()); utxoStorageApi.setDbTx(tx); @@ -305,6 +306,38 @@ const GetAllUtxosMixin = ( }); } // Ergo: + return this.rawGetAllUtxosFromOldDb(tx, deps, _body, derivationTables); + } + rawGetAllUtxosFromOldDb: ( + lf$Transaction, + {| + GetPathWithSpecific: Class, + GetAddress: Class, + GetUtxoTxOutputsWithTx: Class, + GetUtxoAtSafePoint: Class, + GetUtxoDiffToBestBlock: Class, + GetToken: Class, + GetDerivationSpecific: Class, + |}, + IGetAllUtxosRequest, + Map, + ) => Promise = async ( + tx, + deps, + _body, + derivationTables, + ) => { + const addresses = await this.rawGetAllUtxoAddresses( + tx, + { + GetAddress: deps.GetAddress, + GetPathWithSpecific: deps.GetPathWithSpecific, + GetDerivationSpecific: deps.GetDerivationSpecific, + }, + undefined, + derivationTables, + ); + const addressIds = addresses.flatMap(family => family.addrs.map(addr => addr.AddressId)); const utxosInStorage = await deps.GetUtxoTxOutputsWithTx.getUtxo( super.getDb(), tx, @@ -333,6 +366,7 @@ const GetAllUtxosMixin = ( return addressedUtxos; } + getAllUtxos: IGetAllUtxosRequest => Promise = async ( _body ) => { @@ -360,6 +394,33 @@ const GetAllUtxosMixin = ( ); } + getAllUtxosFromOldDb: IGetAllUtxosRequest => Promise = async ( + _body + ) => { + const derivationTables = this.getParent().getDerivationTables(); + const deps = Object.freeze({ + GetPathWithSpecific, + GetAddress, + GetUtxoTxOutputsWithTx, + GetUtxoAtSafePoint, + GetUtxoDiffToBestBlock, + GetToken, + GetDerivationSpecific, + }); + const depTables = Object + .keys(deps) + .map(key => deps[key]) + .flatMap(table => getAllSchemaTables(super.getDb(), table)); + return await raii( + super.getDb(), + [ + ...depTables, + ...mapToTables(super.getDb(), derivationTables), + ], + async tx => this.rawGetAllUtxosFromOldDb(tx, deps, undefined, derivationTables) + ); + } + rawGetAllUtxoAddresses: ( lf$Transaction, {| From c6fd09e95f9d35d113a6435f11d60df05dd42c44 Mon Sep 17 00:00:00 2001 From: yushi Date: Fri, 27 May 2022 12:42:04 +0800 Subject: [PATCH 010/199] add test for migration --- .../app/api/ada/lib/storage/database/index.js | 11 + .../lib/storage/tests/adaMigration.test.js | 56 +- .../ada/lib/storage/tests/testDb.dump.json | 2122 +++++++++++++++++ 3 files changed, 2187 insertions(+), 2 deletions(-) create mode 100644 packages/yoroi-extension/app/api/ada/lib/storage/tests/testDb.dump.json diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js index 0ab01811ab..32dfc9bd1b 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js @@ -156,6 +156,17 @@ export const loadLovefieldDB = async ( return db; }; +export const loadLovefieldDBFromDump = async ( + storeType: $Values, + dump: Object, +): Promise => { + const db = await populateAndCreate(storeType); + + db.import(dump); + + return db; +}; + /** deletes the old database and returns the new database to use */ export async function importOldDb( oldDb: lf$Database, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/tests/adaMigration.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/tests/adaMigration.test.js index 9cf04bb7a3..913ef17623 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/tests/adaMigration.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/tests/adaMigration.test.js @@ -8,9 +8,14 @@ import oldStorageMemory from '../../../../../../features/yoroi_snapshots/histori import oldStorageTrezor from '../../../../../../features/yoroi_snapshots/historical-versions/1_9_0/trezor/localStorage.json'; import oldStorageLedger from '../../../../../../features/yoroi_snapshots/historical-versions/1_9_0/ledger/localStorage.json'; import { RustModule } from '../../cardanoCrypto/rustLoader'; -import { dumpByVersion, loadLovefieldDB } from '../database/index'; -import { storageV2Migration } from '../adaMigration'; +import { + dumpByVersion, + loadLovefieldDB, + loadLovefieldDBFromDump +} from '../database/index'; +import { storageV2Migration, populateNewUtxodata } from '../adaMigration'; import { mockDate, filterDbSnapshot } from '../../../../jestUtils'; +import utxoTestDbDump from './testDb.dump.json'; beforeAll(async () => { await RustModule.load(); @@ -74,3 +79,50 @@ test('Migrate ledger storage v1 to storage v2', async (done) => { await baseTest(db); done(); }); + +test('Migrate to Yoroi-lib UtxoService storage', async () => { + const db = await loadLovefieldDBFromDump(schema.DataStoreType.MEMORY, utxoTestDbDump); + + await populateNewUtxodata(db); + + const dump = await db.export(); + + expect(dump.tables.UtxoAtSafePointTable).toEqual( + [ + { + ConceptualWalletId: 1, + UtxoAtSafePoint: { + lastSafeBlockHash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba26', + utxos: [ + { + utxoId: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed5460', + txHash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', + txIndex: 0, + receiver: 'Ae2tdPwUPEZ3Kt2BJnDMQggxEA4c9MTagByH41rJkv2k82dBch2nqMAdyHJ', + amount: '1100000', + assets: [ + { + assetId: '0ccb954ed44c1cd267f21f628317637679887033564eef61857a0b62.616263', + policyId: '0ccb954ed44c1cd267f21f628317637679887033564eef61857a0b62', + name: '616263', + amount: '1' + } + ], + blockNum: 218609 + }, + { + utxoId: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed5461', + txHash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', + txIndex: 1, + receiver: 'Ae2tdPwUPEYxsngJhnW49jrmGuaCvQK34Hqrnx5w5SWxgfjDkSDcnrRdT5G', + amount: '900000', + assets: [], + blockNum: 218609 + } + ] + }, + UtxoAtSafePointId: 1 + } + ] + ); +}); diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/tests/testDb.dump.json b/packages/yoroi-extension/app/api/ada/lib/storage/tests/testDb.dump.json new file mode 100644 index 0000000000..35583dc0c9 --- /dev/null +++ b/packages/yoroi-extension/app/api/ada/lib/storage/tests/testDb.dump.json @@ -0,0 +1,2122 @@ +{ + "name": "yoroi-schema", + "version": 16, + "tables": { + "Network": [ + { + "NetworkId": 0, + "NetworkName": "Cardano Mainnet", + "Backend": { + "BackendService": "https://iohk-mainnet.yoroiwallet.com", + "WebSocket": "wss://iohk-mainnet.yoroiwallet.com:443", + "TokenInfoService": "https://cdn.yoroiwallet.com" + }, + "BaseConfig": [ + { + "StartAt": 0, + "ChainNetworkId": "1", + "ByronNetworkId": 764824073, + "GenesisDate": "1506203091000", + "SlotsPerEpoch": 21600, + "SlotDuration": 20 + }, + { + "StartAt": 208, + "SlotsPerEpoch": 432000, + "SlotDuration": 1, + "PerEpochPercentageReward": 69344, + "LinearFee": { + "coefficient": "44", + "constant": "155381" + }, + "MinimumUtxoVal": "1000000", + "CoinsPerUtxoWord": "34482", + "PoolDeposit": "500000000", + "KeyDeposit": "2000000" + } + ], + "CoinType": 2147485463, + "Fork": 0 + }, + { + "NetworkId": 100, + "NetworkName": "Jormungandr Mainnet", + "Backend": { + "BackendService": "https://shelley-itn-yoroi-backend.yoroiwallet.com", + "WebSocket": "wss://shelley-itn-yoroi-backend.yoroiwallet.com:443" + }, + "BaseConfig": [ + { + "StartAt": 0, + "Discriminant": 0, + "ChainNetworkId": "8e4d2a343f3dcf9330ad9035b3e8d168e6728904262f2c434a4f8f934ec7b676", + "ByronNetworkId": 764824073, + "GenesisDate": "1576264417000", + "SlotsPerEpoch": 43200, + "SlotDuration": 2, + "PerEpochPercentageReward": 19666, + "LinearFee": { + "constant": "200000", + "coefficient": "100000", + "certificate": "400000", + "per_certificate_fees": { + "certificate_pool_registration": "500000000", + "certificate_stake_delegation": "400000" + } + } + } + ], + "CoinType": 2147485463, + "Fork": 1 + }, + { + "NetworkId": 200, + "NetworkName": "Ergo Mainnet", + "Backend": { + "BackendService": "https://ergo-backend.yoroiwallet.com" + }, + "BaseConfig": [ + { + "StartAt": 0, + "ChainNetworkId": "0", + "MinimumBoxValue": "100000", + "FeeAddress": "031005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573046eebdc83" + } + ], + "CoinType": 2147484077, + "Fork": 0 + }, + { + "NetworkId": 300, + "NetworkName": "Cardano Testnet", + "Backend": { + "BackendService": "https://testnet-backend.yoroiwallet.com", + "WebSocket": "wss://testnet-backend.yoroiwallet.com:443", + "TokenInfoService": "https://stage-cdn.yoroiwallet.com" + }, + "BaseConfig": [ + { + "StartAt": 0, + "ChainNetworkId": "0", + "ByronNetworkId": 1097911063, + "GenesisDate": "1563999616000", + "SlotsPerEpoch": 21600, + "SlotDuration": 20 + }, + { + "StartAt": 74, + "SlotsPerEpoch": 432000, + "SlotDuration": 1, + "PerEpochPercentageReward": 69344, + "LinearFee": { + "coefficient": "44", + "constant": "155381" + }, + "CoinsPerUtxoWord": "34482", + "MinimumUtxoVal": "1000000", + "PoolDeposit": "500000000", + "KeyDeposit": "2000000" + } + ], + "CoinType": 2147485463, + "Fork": 0 + }, + { + "NetworkId": 400, + "NetworkName": "Alonzo Testnet", + "Backend": { + "BackendService": "https://alonzo-backend.yoroiwallet.com", + "WebSocket": "wss://alonzo-backend.yoroiwallet.com:443", + "TokenInfoService": "https://stage-cdn.yoroiwallet.com" + }, + "BaseConfig": [ + { + "StartAt": 0, + "ChainNetworkId": "0", + "ByronNetworkId": 1097911063, + "GenesisDate": "1563999616000", + "SlotsPerEpoch": 21600, + "SlotDuration": 20 + }, + { + "StartAt": 0, + "SlotsPerEpoch": 432000, + "SlotDuration": 1, + "PerEpochPercentageReward": 69344, + "LinearFee": { + "coefficient": "44", + "constant": "155381" + }, + "CoinsPerUtxoWord": "34482", + "MinimumUtxoVal": "1000000", + "PoolDeposit": "500000000", + "KeyDeposit": "2000000" + } + ], + "CoinType": 2147485463, + "Fork": 0 + } + ], + "Key": [ + { + "Hash": "0ab93dd86df170df4df0cfe61f62f29e89d89add5b937ebbe43cdcfb77816f2c3fbe0d10e77ba03198b53d9ce2f33b7bd5dff8a868266f514bf2adc2d437cd339e06de43dbbac2b30656fc1e002dc21141bddc9f5902819b18f9d82a6d8e6920b176fd3a3753efd065a0b931698b2aaf5d06cd8eb617b5e00cab14145f8844af60b2b34268bad953d5555eb62534199a3944de5d72a8c4b1adfd8d01", + "IsEncrypted": true, + "PasswordLastUpdate": null, + "Type": 0, + "KeyId": 1 + }, + { + "Hash": "30037ce72ab2163c2e2d8f864b974c7176f64c53b66a81db18640f267eed13b36fb042cc7a78843824c8d1afddd3ccb3a37be249881bbacf85c3192f2dca7060", + "IsEncrypted": false, + "PasswordLastUpdate": null, + "Type": 0, + "KeyId": 2 + } + ], + "Address": [ + { + "Digest": -5.18814298093809e+161, + "Hash": "Ae2tdPwUPEZCfyggUgSxD1E5UCx5f5hrXCdvQjJszxE7epyZ4ox9vRNUbHf", + "Type": 0, + "AddressId": 1 + }, + { + "Digest": 4.005897187270229e+39, + "Hash": "Ae2tdPwUPEZFXnw5T5aXoaP28yw4mRLeYomaG9mPGCFbPUtw368ZWYKp1zM", + "Type": 0, + "AddressId": 2 + }, + { + "Digest": -1.9091532612969177e-131, + "Hash": "Ae2tdPwUPEZ8gpDazyi8VtcGMnMrkpKxts6ppCT45mdT6WMZEwHXs7pP8Tg", + "Type": 0, + "AddressId": 3 + }, + { + "Digest": -2.400376372464157e+124, + "Hash": "Ae2tdPwUPEZBFhEBZgvm3fQeuW2zBPochfQehFtXn6tCRQy7zsQ9Px88jsH", + "Type": 0, + "AddressId": 4 + }, + { + "Digest": 9.948420953505185e-47, + "Hash": "Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo", + "Type": 0, + "AddressId": 5 + }, + { + "Digest": 9.326602089821879e-30, + "Hash": "Ae2tdPwUPEYxzZH7sSyyXK6DDmjCxRajXUXFqbEjtxfPN7HZzQfXr4hxKwT", + "Type": 0, + "AddressId": 6 + }, + { + "Digest": 4.871145213048404e-43, + "Hash": "Ae2tdPwUPEZMUQdcxu6AEoq9wzL8mtSCKhWUNhYjXqq1aenwimnLaCit1FY", + "Type": 0, + "AddressId": 7 + }, + { + "Digest": 6.75274668731026e+61, + "Hash": "Ae2tdPwUPEYw9VvB1BQqgGd8XzNfgz4mv9gBjB9P8EtvPQfz4D8Gt4xnQog", + "Type": 0, + "AddressId": 8 + }, + { + "Digest": -7.631163428764417e+276, + "Hash": "Ae2tdPwUPEYxYn4T89ffVqpDwqwsMDAZTcfnWBqAkjoem5sgaVKGU3FUB2E", + "Type": 0, + "AddressId": 9 + }, + { + "Digest": -1.6017303425870374e-101, + "Hash": "Ae2tdPwUPEZBZ9aYboQuf5PFcjuZRCosBhLkUCxSQw1vqk3iyK5qGo5CJWe", + "Type": 0, + "AddressId": 10 + }, + { + "Digest": -6.537349771934158e+223, + "Hash": "Ae2tdPwUPEZ4yCaYs3j7pPPaM9iA3KLruJkKjmUBBLKYzpJUHutWdS6EkKa", + "Type": 0, + "AddressId": 11 + }, + { + "Digest": -5.3866774008751e-237, + "Hash": "Ae2tdPwUPEZ5zV8wqC9gPvkDtr2MS3Ys2vQbyPdM4wsdnzevbqnaJFg62hk", + "Type": 0, + "AddressId": 12 + }, + { + "Digest": 1.382203475035168e-237, + "Hash": "Ae2tdPwUPEZAD1KmgTQ4zS6Q5s4VXWDXqB3ZmM1abQGoyrGN8qCsZyXc3vf", + "Type": 0, + "AddressId": 13 + }, + { + "Digest": 2.490260903712286e+254, + "Hash": "Ae2tdPwUPEZ1SFwQMGroCTabfCPJ1RiK8D2F11C1vTMi14tX1XK2uogRWqM", + "Type": 0, + "AddressId": 14 + }, + { + "Digest": 1.1423435570016581e+77, + "Hash": "Ae2tdPwUPEZJ8CiSFVipoupUVBGRAVbmbpmKPCXsExcwzZd6FcNpAcExY1r", + "Type": 0, + "AddressId": 15 + }, + { + "Digest": 1.1025868598519032e-268, + "Hash": "Ae2tdPwUPEZCeLRPNcreMQAAfJFs9ZzBeFK9pUXhMnL3ooQP9TajasVsYKK", + "Type": 0, + "AddressId": 16 + }, + { + "Digest": 6.802372910540179e+76, + "Hash": "Ae2tdPwUPEZGvzYwTSUZhTxDviZSKqLZDYVkcT4rLqckFXvdTMv21CinTCd", + "Type": 0, + "AddressId": 17 + }, + { + "Digest": 1.0898173340677806e+293, + "Hash": "Ae2tdPwUPEZKEsQJxXQvRJHLCWRGFQNWFgHhroEw9GGXV25wRBH8TAmwCRi", + "Type": 0, + "AddressId": 18 + }, + { + "Digest": -1.4835020475302562e+52, + "Hash": "Ae2tdPwUPEZ6u2i2u4iLhd9Rq4rUBsVaMpqQWuTCXMitMmugcsr9iANjNEE", + "Type": 0, + "AddressId": 19 + }, + { + "Digest": 1.0753995066668421e+154, + "Hash": "Ae2tdPwUPEYxsngJhnW49jrmGuaCvQK34Hqrnx5w5SWxgfjDkSDcnrRdT5G", + "Type": 0, + "AddressId": 20 + }, + { + "Digest": 1.5842207841682243e-196, + "Hash": "Ae2tdPwUPEZ3Kt2BJnDMQggxEA4c9MTagByH41rJkv2k82dBch2nqMAdyHJ", + "Type": 0, + "AddressId": 21 + }, + { + "Digest": 1.8819557589593857e+126, + "Hash": "Ae2tdPwUPEYzRJEAdyX24mYTmKx8dMHYQgxkBcgnsdFHPPvmySMS9cspZmj", + "Type": 0, + "AddressId": 22 + }, + { + "Digest": -1.6768419216602427e+93, + "Hash": "Ae2tdPwUPEZ5yxTy5wcVmUXP5xkkDSXfrgqWp8Px6knmUiFjJEytBGXfWoS", + "Type": 0, + "AddressId": 23 + }, + { + "Digest": -2.5950200694100975e-154, + "Hash": "Ae2tdPwUPEZ3pKEVL1tMmz8EzaE9Hyn2cVJKf3TFVCjw7xeLFBdJngUXPXG", + "Type": 0, + "AddressId": 24 + }, + { + "Digest": -6.28195010657389e+125, + "Hash": "Ae2tdPwUPEZLhFMGWjMpCWwsUiN58DkKmtg6r7dFJfXhmUyzNGPb2Crg141", + "Type": 0, + "AddressId": 25 + }, + { + "Digest": 1.8038527609850763e-30, + "Hash": "Ae2tdPwUPEZBTYWkpudmAYdyQM9CHcV9orEVnVSan6EP2xNApB5bLE1XsKF", + "Type": 0, + "AddressId": 26 + }, + { + "Digest": -3.2738732077215015e+237, + "Hash": "Ae2tdPwUPEYzdEoDVNdvLz6kFgVsjEyUJ1z3mHzAT1HotQk31tRvGNM4tge", + "Type": 0, + "AddressId": 27 + }, + { + "Digest": 6.009965811065196e+234, + "Hash": "Ae2tdPwUPEZ2NvzdECB18z3SBTc8SVvx1PJuxPKsnncygEZeeKNaAeBeLDg", + "Type": 0, + "AddressId": 28 + }, + { + "Digest": -3.412056347004804e-256, + "Hash": "Ae2tdPwUPEZ3iMfnFWSM3zeqpNwAdT62jNgiA27x1ZEacpfVbViYWb7gyNP", + "Type": 0, + "AddressId": 29 + }, + { + "Digest": -1.4607797017835113e-25, + "Hash": "Ae2tdPwUPEYykz6FbkuRf9ScRwZc8F1xobMPyzECjsdMyqobn2tVpL6pdDW", + "Type": 0, + "AddressId": 30 + }, + { + "Digest": -1.5492234544082762e+72, + "Hash": "Ae2tdPwUPEZ8SUET76NhRhJ6TNLW48csZ4e87hu6JYobQvVTKSYDMPBU5Xa", + "Type": 0, + "AddressId": 31 + }, + { + "Digest": 6.397199081168596e-249, + "Hash": "Ae2tdPwUPEZBryPRKuib729PQNSBxzgXsTVwmg3CWQjZaAS7pj4WZNa3VR6", + "Type": 0, + "AddressId": 32 + }, + { + "Digest": 2.699654694740964e-163, + "Hash": "Ae2tdPwUPEZ6yyMqXFEYmLvqXqktpU8LR6oW8qVbaap7R2YJGc9xDbs2Eb6", + "Type": 0, + "AddressId": 33 + }, + { + "Digest": 3.2672025499521527e-301, + "Hash": "Ae2tdPwUPEZ4gRK6Qhj3Mo4tSU4LkRiqVgsQm6rg8YBGC6fqVLNqA9REr7c", + "Type": 0, + "AddressId": 34 + }, + { + "Digest": 5.473082144527723e-299, + "Hash": "Ae2tdPwUPEZ3xWm3GHFVNtVWcnyTGpSF6AjyhZJY3DuSGvf3WvwLzkBisFw", + "Type": 0, + "AddressId": 35 + }, + { + "Digest": -9.504830601258996e-107, + "Hash": "Ae2tdPwUPEZL9BqYXyY6zQQoatsWbkcN6g46NfvCfaE8mQppcVgentdAXeo", + "Type": 0, + "AddressId": 36 + }, + { + "Digest": -2.255481392460208e-13, + "Hash": "Ae2tdPwUPEYzxRSsoqFY2Ym7zQfSdosY1bdK8KZTEjPjcobHd3GbZ3qZpvB", + "Type": 0, + "AddressId": 37 + }, + { + "Digest": -3.382255898780831e+65, + "Hash": "Ae2tdPwUPEZEdntBfWRH5ucNrQd7RwPjZ2QZEkxJbsJDBrCGnTtjqfb8Aph", + "Type": 0, + "AddressId": 38 + }, + { + "Digest": 6.040400230567571e+220, + "Hash": "Ae2tdPwUPEZKDMDuUZBUSokAzeJwNpqHorYEPuycXBMMFameoVFkxwpLheC", + "Type": 0, + "AddressId": 39 + }, + { + "Digest": -1.2798377351521785e-185, + "Hash": "Ae2tdPwUPEZ2E4XNPVarvndsmkC8n2vS9rXJfCxny9oVacViLRDpeKioaAc", + "Type": 0, + "AddressId": 40 + }, + { + "Digest": -1.3416871383074957e-161, + "Hash": "Ae2tdPwUPEYxD5EgazsN7DcLp2fFiak1RTco6CoDNioKVuodftS4oNeabwz", + "Type": 0, + "AddressId": 41 + }, + { + "Digest": 2.414293239739638e-47, + "Hash": "Ae2tdPwUPEZFqcMbS757FyZrABDZUVtzEW4sPvzZj3UYBcn8BSWFBxmQK2n", + "Type": 0, + "AddressId": 42 + }, + { + "Digest": 1.1036862858872953e+216, + "Hash": "Ae2tdPwUPEZGR3uPidVc5p1cSnV9KDzDxrrembeFhLYMgFAa4KHpTqmncFQ", + "Type": 0, + "AddressId": 43 + }, + { + "Digest": -2.938948476649332e-97, + "Hash": "Ae2tdPwUPEZEvn7nMBBaBMCc773zeMCPsS93tDsFvyZw4quRbnq5bnP1i8F", + "Type": 0, + "AddressId": 44 + }, + { + "Digest": 8.159203653530658e-92, + "Hash": "Ae2tdPwUPEZAzQ7FdZ8ksAjK7iVGzjMKUxWqkQLF52oGxWBNfyDRocqJwBT", + "Type": 0, + "AddressId": 45 + }, + { + "Digest": -1.2688708748746819e-61, + "Hash": "Ae2tdPwUPEZ5PxKxoyZDgjsKgMWMpTRa4PH3sVgARSGBsWwNBH3qg7cMFsP", + "Type": 0, + "AddressId": 46 + }, + { + "Digest": 3.0263387081779602e-151, + "Hash": "Ae2tdPwUPEZE9RAm3d3zuuh22YjqDxhR1JF6G93uJsRrk51QGHzRUzLvDjL", + "Type": 0, + "AddressId": 47 + }, + { + "Digest": 3.748999811104187e+228, + "Hash": "Ae2tdPwUPEZ4JpbhPGtK8RZyMLGq7R6wVBHErTxBYUuSKyUbhc7fegtU3dF", + "Type": 0, + "AddressId": 48 + }, + { + "Digest": 2.6120787617673514e-30, + "Hash": "Ae2tdPwUPEZBdtWRV8KXaHdiV7DL5pG96iETwYoBy1My4VLBCyU9k5kMUfJ", + "Type": 0, + "AddressId": 49 + }, + { + "Digest": -1.0123492942107734e-165, + "Hash": "Ae2tdPwUPEYy1r4T3wX9KmWPA5XP9dgxCiZ2Dz6SbS1tgxpwkHLr7EutFSi", + "Type": 0, + "AddressId": 50 + }, + { + "Digest": -3.074319404454647e-200, + "Hash": "Ae2tdPwUPEZE9zHeyCoYJF2AViqdj4iz1LzzrZaNDGqsBGaVMvzFrnvhmjC", + "Type": 0, + "AddressId": 51 + }, + { + "Digest": -1.488820380175102e+175, + "Hash": "Ae2tdPwUPEYzrgMT2ZCnohBmEkhL4WETjSo7pJJv3tifWP2yD8HcbRpcsuz", + "Type": 0, + "AddressId": 52 + }, + { + "Digest": 2.3902554625601016e-104, + "Hash": "Ae2tdPwUPEYxhJZmF3nx3hd8KuWWg4wxTuCzHiwELTMz5DNj2RVVp3GvSXk", + "Type": 0, + "AddressId": 53 + }, + { + "Digest": 2.909739229935528e-145, + "Hash": "Ae2tdPwUPEZ1QKYKaWjQQYhLY9C9hQQjiEAYj8X6VszRheb1uzdUJ1PQi5A", + "Type": 0, + "AddressId": 54 + }, + { + "Digest": -3.512532923970995e-268, + "Hash": "Ae2tdPwUPEZJxz9udJ5AiGNAypTimc5tW8aeHMmXx1Gz1i8vXT7ds1CJePD", + "Type": 0, + "AddressId": 55 + }, + { + "Digest": -1.4587970711938662e-273, + "Hash": "Ae2tdPwUPEZ1CQ8Mxz3xCVwg11dxMHLVsfsyaq12wTuwyTsVw55EFHgTRba", + "Type": 0, + "AddressId": 56 + }, + { + "Digest": -5.101875829381085e-120, + "Hash": "Ae2tdPwUPEZ5MLs4hNC7W7hRWZzDaNa3XT7nae5FBA2W3We7JbAKtTYhcg2", + "Type": 0, + "AddressId": 57 + }, + { + "Digest": 2.5502116477786873e+76, + "Hash": "Ae2tdPwUPEZETaqb1tkBX7XxtuLcda2RHsG4G8DsDQdNeV5SSWcc1MJcraP", + "Type": 0, + "AddressId": 58 + }, + { + "Digest": -4.51175714903157e+254, + "Hash": "Ae2tdPwUPEZ8d86PnvnuKuT1v3Fqy4UEtcAtHcYpzCfa6SQX7jkW4hjXQZw", + "Type": 0, + "AddressId": 59 + }, + { + "Digest": 1.1003732905385264e+77, + "Hash": "Ae2tdPwUPEZL8tfFQnZpYNVZPx58ix3TsJbRGudMqqiesmyYDyQiMP7p4V3", + "Type": 0, + "AddressId": 60 + }, + { + "Digest": -3.183586286561401e+74, + "Hash": "Ae2tdPwUPEZBWaoj9gwh2jtmQSBr2giZVTyt3J5pyRh9wcn971ZugRG8ZHB", + "Type": 0, + "AddressId": 61 + }, + { + "Digest": -1.7284293980886233e+143, + "Hash": "Ae2tdPwUPEZ2oSkjKqo3Z4CUQJg8xxP5fZnjf1mmqpGWB9FS84kjUnziRQG", + "Type": 0, + "AddressId": 62 + }, + { + "Digest": 12486396901266293000, + "Hash": "Ae2tdPwUPEZELvmKtHpL6HAWt5H2dYc4hGf8yHJKuyWSocqVHhmhVHaf6nX", + "Type": 0, + "AddressId": 63 + }, + { + "Digest": 5.1141046639172445e+261, + "Hash": "619a57f784ef8f9a9d3d25a905e4df27d46843d7a0b93d162cdfae6cdc", + "Type": 3, + "AddressId": 64 + } + ], + "EncryptionMeta": [ + { + "AddressSeed": 1690513609, + "TransactionSeed": 769388545, + "BlockSeed": 371536492, + "TokenSeed": 493718318, + "EncryptionMetaId": 0 + } + ], + "KeyDerivation": [ + { + "PublicKeyId": null, + "PrivateKeyId": 1, + "Parent": null, + "Index": null, + "KeyDerivationId": 1 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 1, + "Index": 2147483692, + "KeyDerivationId": 2 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 2, + "Index": 2147485463, + "KeyDerivationId": 3 + }, + { + "PublicKeyId": 2, + "PrivateKeyId": null, + "Parent": 3, + "Index": 2147483648, + "KeyDerivationId": 4 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 4, + "Index": 0, + "KeyDerivationId": 5 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 0, + "KeyDerivationId": 6 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 1, + "KeyDerivationId": 7 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 2, + "KeyDerivationId": 8 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 3, + "KeyDerivationId": 9 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 4, + "KeyDerivationId": 10 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 5, + "KeyDerivationId": 11 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 6, + "KeyDerivationId": 12 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 7, + "KeyDerivationId": 13 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 8, + "KeyDerivationId": 14 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 9, + "KeyDerivationId": 15 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 10, + "KeyDerivationId": 16 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 11, + "KeyDerivationId": 17 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 12, + "KeyDerivationId": 18 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 13, + "KeyDerivationId": 19 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 14, + "KeyDerivationId": 20 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 15, + "KeyDerivationId": 21 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 16, + "KeyDerivationId": 22 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 17, + "KeyDerivationId": 23 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 18, + "KeyDerivationId": 24 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 19, + "KeyDerivationId": 25 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 4, + "Index": 1, + "KeyDerivationId": 26 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 0, + "KeyDerivationId": 27 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 1, + "KeyDerivationId": 28 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 2, + "KeyDerivationId": 29 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 3, + "KeyDerivationId": 30 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 4, + "KeyDerivationId": 31 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 5, + "KeyDerivationId": 32 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 6, + "KeyDerivationId": 33 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 7, + "KeyDerivationId": 34 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 8, + "KeyDerivationId": 35 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 9, + "KeyDerivationId": 36 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 10, + "KeyDerivationId": 37 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 11, + "KeyDerivationId": 38 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 12, + "KeyDerivationId": 39 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 13, + "KeyDerivationId": 40 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 14, + "KeyDerivationId": 41 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 15, + "KeyDerivationId": 42 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 16, + "KeyDerivationId": 43 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 17, + "KeyDerivationId": 44 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 18, + "KeyDerivationId": 45 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 19, + "KeyDerivationId": 46 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 20, + "KeyDerivationId": 47 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 21, + "KeyDerivationId": 48 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 22, + "KeyDerivationId": 49 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 23, + "KeyDerivationId": 50 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 24, + "KeyDerivationId": 51 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 25, + "KeyDerivationId": 52 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 26, + "KeyDerivationId": 53 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 27, + "KeyDerivationId": 54 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 28, + "KeyDerivationId": 55 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 29, + "KeyDerivationId": 56 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 30, + "KeyDerivationId": 57 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 31, + "KeyDerivationId": 58 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 32, + "KeyDerivationId": 59 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 33, + "KeyDerivationId": 60 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 34, + "KeyDerivationId": 61 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 35, + "KeyDerivationId": 62 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 36, + "KeyDerivationId": 63 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 37, + "KeyDerivationId": 64 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 38, + "KeyDerivationId": 65 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 5, + "Index": 39, + "KeyDerivationId": 66 + }, + { + "PublicKeyId": null, + "PrivateKeyId": null, + "Parent": 26, + "Index": 20, + "KeyDerivationId": 67 + } + ], + "Block": [ + { + "Hash": "a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba25", + "BlockTime": "2019-09-13T16:37:16.000Z", + "Height": 218608, + "SlotNum": 219650, + "Digest": -9.690691258114666e-56, + "BlockId": 1 + }, + { + "Hash": "a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba26", + "BlockTime": "2019-09-13T16:37:36.000Z", + "Height": 218609, + "SlotNum": 219651, + "Digest": -6.352946211521613e-51, + "BlockId": 2 + } + ], + "Transaction": [ + { + "Type": 0, + "Extra": null, + "BlockId": 1, + "Hash": "29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545", + "Digest": 8.191593645542673e-27, + "Ordinal": 0, + "LastUpdateTime": 1568392636000, + "Status": 1, + "ErrorMessage": null, + "TransactionId": 1 + }, + { + "Type": 1, + "Extra": { + "Fee": "100000", + "Metadata": null, + "IsValid": true + }, + "BlockId": 2, + "Hash": "29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546", + "Digest": 1.249559827714551e-31, + "Ordinal": 0, + "LastUpdateTime": 1568392656000, + "Status": 1, + "ErrorMessage": null, + "TransactionId": 2 + } + ], + "CanonicalAddress": [ + { + "KeyDerivationId": 6, + "CanonicalAddressId": 1 + }, + { + "KeyDerivationId": 7, + "CanonicalAddressId": 2 + }, + { + "KeyDerivationId": 8, + "CanonicalAddressId": 3 + }, + { + "KeyDerivationId": 9, + "CanonicalAddressId": 4 + }, + { + "KeyDerivationId": 10, + "CanonicalAddressId": 5 + }, + { + "KeyDerivationId": 11, + "CanonicalAddressId": 6 + }, + { + "KeyDerivationId": 12, + "CanonicalAddressId": 7 + }, + { + "KeyDerivationId": 13, + "CanonicalAddressId": 8 + }, + { + "KeyDerivationId": 14, + "CanonicalAddressId": 9 + }, + { + "KeyDerivationId": 15, + "CanonicalAddressId": 10 + }, + { + "KeyDerivationId": 16, + "CanonicalAddressId": 11 + }, + { + "KeyDerivationId": 17, + "CanonicalAddressId": 12 + }, + { + "KeyDerivationId": 18, + "CanonicalAddressId": 13 + }, + { + "KeyDerivationId": 19, + "CanonicalAddressId": 14 + }, + { + "KeyDerivationId": 20, + "CanonicalAddressId": 15 + }, + { + "KeyDerivationId": 21, + "CanonicalAddressId": 16 + }, + { + "KeyDerivationId": 22, + "CanonicalAddressId": 17 + }, + { + "KeyDerivationId": 23, + "CanonicalAddressId": 18 + }, + { + "KeyDerivationId": 24, + "CanonicalAddressId": 19 + }, + { + "KeyDerivationId": 25, + "CanonicalAddressId": 20 + }, + { + "KeyDerivationId": 27, + "CanonicalAddressId": 21 + }, + { + "KeyDerivationId": 28, + "CanonicalAddressId": 22 + }, + { + "KeyDerivationId": 29, + "CanonicalAddressId": 23 + }, + { + "KeyDerivationId": 30, + "CanonicalAddressId": 24 + }, + { + "KeyDerivationId": 31, + "CanonicalAddressId": 25 + }, + { + "KeyDerivationId": 32, + "CanonicalAddressId": 26 + }, + { + "KeyDerivationId": 33, + "CanonicalAddressId": 27 + }, + { + "KeyDerivationId": 34, + "CanonicalAddressId": 28 + }, + { + "KeyDerivationId": 35, + "CanonicalAddressId": 29 + }, + { + "KeyDerivationId": 36, + "CanonicalAddressId": 30 + }, + { + "KeyDerivationId": 37, + "CanonicalAddressId": 31 + }, + { + "KeyDerivationId": 38, + "CanonicalAddressId": 32 + }, + { + "KeyDerivationId": 39, + "CanonicalAddressId": 33 + }, + { + "KeyDerivationId": 40, + "CanonicalAddressId": 34 + }, + { + "KeyDerivationId": 41, + "CanonicalAddressId": 35 + }, + { + "KeyDerivationId": 42, + "CanonicalAddressId": 36 + }, + { + "KeyDerivationId": 43, + "CanonicalAddressId": 37 + }, + { + "KeyDerivationId": 44, + "CanonicalAddressId": 38 + }, + { + "KeyDerivationId": 45, + "CanonicalAddressId": 39 + }, + { + "KeyDerivationId": 46, + "CanonicalAddressId": 40 + }, + { + "KeyDerivationId": 47, + "CanonicalAddressId": 41 + }, + { + "KeyDerivationId": 48, + "CanonicalAddressId": 42 + }, + { + "KeyDerivationId": 49, + "CanonicalAddressId": 43 + }, + { + "KeyDerivationId": 50, + "CanonicalAddressId": 44 + }, + { + "KeyDerivationId": 51, + "CanonicalAddressId": 45 + }, + { + "KeyDerivationId": 52, + "CanonicalAddressId": 46 + }, + { + "KeyDerivationId": 53, + "CanonicalAddressId": 47 + }, + { + "KeyDerivationId": 54, + "CanonicalAddressId": 48 + }, + { + "KeyDerivationId": 55, + "CanonicalAddressId": 49 + }, + { + "KeyDerivationId": 56, + "CanonicalAddressId": 50 + }, + { + "KeyDerivationId": 57, + "CanonicalAddressId": 51 + }, + { + "KeyDerivationId": 58, + "CanonicalAddressId": 52 + }, + { + "KeyDerivationId": 59, + "CanonicalAddressId": 53 + }, + { + "KeyDerivationId": 60, + "CanonicalAddressId": 54 + }, + { + "KeyDerivationId": 61, + "CanonicalAddressId": 55 + }, + { + "KeyDerivationId": 62, + "CanonicalAddressId": 56 + }, + { + "KeyDerivationId": 63, + "CanonicalAddressId": 57 + }, + { + "KeyDerivationId": 64, + "CanonicalAddressId": 58 + }, + { + "KeyDerivationId": 65, + "CanonicalAddressId": 59 + }, + { + "KeyDerivationId": 66, + "CanonicalAddressId": 60 + }, + { + "KeyDerivationId": 67, + "CanonicalAddressId": 61 + } + ], + "AddressMapping": [ + { + "KeyDerivationId": 6, + "AddressId": 1, + "AddressMappingId": 1 + }, + { + "KeyDerivationId": 7, + "AddressId": 2, + "AddressMappingId": 2 + }, + { + "KeyDerivationId": 8, + "AddressId": 3, + "AddressMappingId": 3 + }, + { + "KeyDerivationId": 9, + "AddressId": 4, + "AddressMappingId": 4 + }, + { + "KeyDerivationId": 10, + "AddressId": 5, + "AddressMappingId": 5 + }, + { + "KeyDerivationId": 11, + "AddressId": 6, + "AddressMappingId": 6 + }, + { + "KeyDerivationId": 12, + "AddressId": 7, + "AddressMappingId": 7 + }, + { + "KeyDerivationId": 13, + "AddressId": 8, + "AddressMappingId": 8 + }, + { + "KeyDerivationId": 14, + "AddressId": 9, + "AddressMappingId": 9 + }, + { + "KeyDerivationId": 15, + "AddressId": 10, + "AddressMappingId": 10 + }, + { + "KeyDerivationId": 16, + "AddressId": 11, + "AddressMappingId": 11 + }, + { + "KeyDerivationId": 17, + "AddressId": 12, + "AddressMappingId": 12 + }, + { + "KeyDerivationId": 18, + "AddressId": 13, + "AddressMappingId": 13 + }, + { + "KeyDerivationId": 19, + "AddressId": 14, + "AddressMappingId": 14 + }, + { + "KeyDerivationId": 20, + "AddressId": 15, + "AddressMappingId": 15 + }, + { + "KeyDerivationId": 21, + "AddressId": 16, + "AddressMappingId": 16 + }, + { + "KeyDerivationId": 22, + "AddressId": 17, + "AddressMappingId": 17 + }, + { + "KeyDerivationId": 23, + "AddressId": 18, + "AddressMappingId": 18 + }, + { + "KeyDerivationId": 24, + "AddressId": 19, + "AddressMappingId": 19 + }, + { + "KeyDerivationId": 25, + "AddressId": 20, + "AddressMappingId": 20 + }, + { + "KeyDerivationId": 27, + "AddressId": 21, + "AddressMappingId": 21 + }, + { + "KeyDerivationId": 28, + "AddressId": 22, + "AddressMappingId": 22 + }, + { + "KeyDerivationId": 29, + "AddressId": 23, + "AddressMappingId": 23 + }, + { + "KeyDerivationId": 30, + "AddressId": 24, + "AddressMappingId": 24 + }, + { + "KeyDerivationId": 31, + "AddressId": 25, + "AddressMappingId": 25 + }, + { + "KeyDerivationId": 32, + "AddressId": 26, + "AddressMappingId": 26 + }, + { + "KeyDerivationId": 33, + "AddressId": 27, + "AddressMappingId": 27 + }, + { + "KeyDerivationId": 34, + "AddressId": 28, + "AddressMappingId": 28 + }, + { + "KeyDerivationId": 35, + "AddressId": 29, + "AddressMappingId": 29 + }, + { + "KeyDerivationId": 36, + "AddressId": 30, + "AddressMappingId": 30 + }, + { + "KeyDerivationId": 37, + "AddressId": 31, + "AddressMappingId": 31 + }, + { + "KeyDerivationId": 38, + "AddressId": 32, + "AddressMappingId": 32 + }, + { + "KeyDerivationId": 39, + "AddressId": 33, + "AddressMappingId": 33 + }, + { + "KeyDerivationId": 40, + "AddressId": 34, + "AddressMappingId": 34 + }, + { + "KeyDerivationId": 41, + "AddressId": 35, + "AddressMappingId": 35 + }, + { + "KeyDerivationId": 42, + "AddressId": 36, + "AddressMappingId": 36 + }, + { + "KeyDerivationId": 43, + "AddressId": 37, + "AddressMappingId": 37 + }, + { + "KeyDerivationId": 44, + "AddressId": 38, + "AddressMappingId": 38 + }, + { + "KeyDerivationId": 45, + "AddressId": 39, + "AddressMappingId": 39 + }, + { + "KeyDerivationId": 46, + "AddressId": 40, + "AddressMappingId": 40 + }, + { + "KeyDerivationId": 47, + "AddressId": 41, + "AddressMappingId": 41 + }, + { + "KeyDerivationId": 48, + "AddressId": 42, + "AddressMappingId": 42 + }, + { + "KeyDerivationId": 49, + "AddressId": 43, + "AddressMappingId": 43 + }, + { + "KeyDerivationId": 50, + "AddressId": 44, + "AddressMappingId": 44 + }, + { + "KeyDerivationId": 51, + "AddressId": 45, + "AddressMappingId": 45 + }, + { + "KeyDerivationId": 52, + "AddressId": 48, + "AddressMappingId": 46 + }, + { + "KeyDerivationId": 53, + "AddressId": 49, + "AddressMappingId": 47 + }, + { + "KeyDerivationId": 54, + "AddressId": 50, + "AddressMappingId": 48 + }, + { + "KeyDerivationId": 55, + "AddressId": 51, + "AddressMappingId": 49 + }, + { + "KeyDerivationId": 56, + "AddressId": 52, + "AddressMappingId": 50 + }, + { + "KeyDerivationId": 57, + "AddressId": 53, + "AddressMappingId": 51 + }, + { + "KeyDerivationId": 58, + "AddressId": 54, + "AddressMappingId": 52 + }, + { + "KeyDerivationId": 59, + "AddressId": 55, + "AddressMappingId": 53 + }, + { + "KeyDerivationId": 60, + "AddressId": 56, + "AddressMappingId": 54 + }, + { + "KeyDerivationId": 61, + "AddressId": 57, + "AddressMappingId": 55 + }, + { + "KeyDerivationId": 62, + "AddressId": 58, + "AddressMappingId": 56 + }, + { + "KeyDerivationId": 63, + "AddressId": 59, + "AddressMappingId": 57 + }, + { + "KeyDerivationId": 64, + "AddressId": 60, + "AddressMappingId": 58 + }, + { + "KeyDerivationId": 65, + "AddressId": 61, + "AddressMappingId": 59 + }, + { + "KeyDerivationId": 66, + "AddressId": 62, + "AddressMappingId": 60 + }, + { + "KeyDerivationId": 67, + "AddressId": 63, + "AddressMappingId": 61 + } + ], + "Certificate": [], + "CertificateAddress": [], + "Token": [ + { + "Digest": 6.262633522161549e-167, + "NetworkId": 0, + "Identifier": "", + "IsDefault": true, + "IsNFT": false, + "Metadata": { + "type": "Cardano", + "policyId": "", + "assetName": "", + "ticker": "ADA", + "longName": null, + "numberOfDecimals": 6 + }, + "TokenId": 1 + }, + { + "Digest": 6.262633522161549e-167, + "NetworkId": 100, + "Identifier": "", + "IsDefault": true, + "IsNFT": false, + "Metadata": { + "type": "Cardano", + "policyId": "", + "assetName": "", + "ticker": "ADA", + "longName": null, + "numberOfDecimals": 6 + }, + "TokenId": 2 + }, + { + "Digest": 6.262633522161549e-167, + "NetworkId": 200, + "Identifier": "", + "IsDefault": true, + "IsNFT": false, + "Metadata": { + "type": "Ergo", + "height": 0, + "boxId": "", + "ticker": "ERG", + "longName": null, + "numberOfDecimals": 9, + "description": null + }, + "TokenId": 3 + }, + { + "Digest": 6.262633522161549e-167, + "NetworkId": 300, + "Identifier": "", + "IsDefault": true, + "IsNFT": false, + "Metadata": { + "type": "Cardano", + "policyId": "", + "assetName": "", + "ticker": "TADA", + "longName": null, + "numberOfDecimals": 6 + }, + "TokenId": 4 + }, + { + "Digest": 6.262633522161549e-167, + "NetworkId": 400, + "Identifier": "", + "IsDefault": true, + "IsNFT": false, + "Metadata": { + "type": "Cardano", + "policyId": "", + "assetName": "", + "ticker": "TADA", + "longName": null, + "numberOfDecimals": 6 + }, + "TokenId": 5 + }, + { + "Digest": -9.311162321367395e-257, + "NetworkId": 0, + "Identifier": "0ccb954ed44c1cd267f21f628317637679887033564eef61857a0b62.616263", + "IsDefault": false, + "TokenId": 6, + "IsNFT": false, + "Metadata": { + "type": "Cardano", + "ticker": null, + "longName": null, + "numberOfDecimals": 0, + "assetName": "616263", + "policyId": "0ccb954ed44c1cd267f21f628317637679887033564eef61857a0b62", + "lastUpdatedAt": null, + "assetMintMetadata": [] + } + } + ], + "TokenList": [ + { + "Amount": "4000000", + "ListId": 0, + "TokenId": 1, + "TokenListItemId": 1 + }, + { + "Amount": "1", + "ListId": 0, + "TokenId": 6, + "TokenListItemId": 2 + }, + { + "Amount": "2100000", + "ListId": 1, + "TokenId": 1, + "TokenListItemId": 3 + }, + { + "Amount": "1", + "ListId": 1, + "TokenId": 6, + "TokenListItemId": 4 + }, + { + "Amount": "1731391", + "ListId": 2, + "TokenId": 1, + "TokenListItemId": 5 + }, + { + "Amount": "2100000", + "ListId": 3, + "TokenId": 1, + "TokenListItemId": 6 + }, + { + "Amount": "1", + "ListId": 3, + "TokenId": 6, + "TokenListItemId": 7 + }, + { + "Amount": "1000", + "ListId": 4, + "TokenId": 1, + "TokenListItemId": 8 + }, + { + "Amount": "1100000", + "ListId": 5, + "TokenId": 1, + "TokenListItemId": 9 + }, + { + "Amount": "1", + "ListId": 5, + "TokenId": 6, + "TokenListItemId": 10 + }, + { + "Amount": "900000", + "ListId": 6, + "TokenId": 1, + "TokenListItemId": 11 + }, + { + "Amount": "900000", + "ListId": 7, + "TokenId": 1, + "TokenListItemId": 12 + } + ], + "ConceptualWallet": [ + { + "NetworkId": 0, + "Name": "My Test Wallet", + "ConceptualWalletId": 1 + } + ], + "PublicDeriver": [ + { + "ConceptualWalletId": 1, + "KeyDerivationId": 4, + "Name": "", + "Index": 0, + "LastSyncInfoId": 1, + "PublicDeriverId": 1 + } + ], + "LastSyncInfo": [ + { + "LastSyncInfoId": 1, + "Time": "1970-01-01T00:00:00.001Z", + "SlotNum": 219651, + "BlockHash": "a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba26", + "Height": 218609 + } + ], + "HwWalletMeta": [], + "RootDerivation": [ + { + "KeyDerivationId": 1, + "RootDerivationId": 1 + } + ], + "PurposeDerivation": [ + { + "KeyDerivationId": 2, + "PurposeDerivationId": 1 + } + ], + "CoinTypeDerivation": [ + { + "KeyDerivationId": 3, + "CoinTypeDerivationId": 1 + } + ], + "Bip44Account": [ + { + "KeyDerivationId": 4, + "Bip44AccountId": 1 + } + ], + "Bip44Chain": [ + { + "Bip44ChainId": 1, + "KeyDerivationId": 5, + "DisplayCutoff": 19 + }, + { + "KeyDerivationId": 26, + "DisplayCutoff": null, + "Bip44ChainId": 2 + } + ], + "Bip44Wrapper": [ + { + "ConceptualWalletId": 1, + "SignerLevel": 0, + "PublicDeriverLevel": 3, + "PrivateDeriverKeyDerivationId": 1, + "PrivateDeriverLevel": 0, + "RootKeyDerivationId": 1 + } + ], + "Cip1852Wrapper": [], + "UtxoTransactionInput": [ + { + "TransactionId": 1, + "AddressId": 46, + "ParentTxHash": "9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d20", + "IndexInParentTx": 0, + "IndexInOwnTx": 0, + "TokenListId": 0, + "UtxoTransactionInputId": 1 + }, + { + "TransactionId": 2, + "AddressId": 5, + "ParentTxHash": "29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545", + "IndexInParentTx": 0, + "IndexInOwnTx": 0, + "TokenListId": 3, + "UtxoTransactionInputId": 2 + } + ], + "UtxoTransactionOutput": [ + { + "UtxoTransactionOutputId": 1, + "TransactionId": 1, + "AddressId": 5, + "OutputIndex": 0, + "TokenListId": 1, + "IsUnspent": false, + "ErgoBoxId": null, + "ErgoCreationHeight": null, + "ErgoTree": null, + "ErgoRegisters": null + }, + { + "TransactionId": 1, + "AddressId": 47, + "OutputIndex": 1, + "IsUnspent": true, + "ErgoBoxId": null, + "ErgoCreationHeight": null, + "ErgoRegisters": null, + "ErgoTree": null, + "TokenListId": 2, + "UtxoTransactionOutputId": 2 + }, + { + "TransactionId": 2, + "AddressId": 21, + "OutputIndex": 0, + "TokenListId": 5, + "IsUnspent": true, + "ErgoBoxId": null, + "ErgoCreationHeight": null, + "ErgoRegisters": null, + "ErgoTree": null, + "UtxoTransactionOutputId": 3 + }, + { + "TransactionId": 2, + "AddressId": 20, + "OutputIndex": 1, + "TokenListId": 6, + "IsUnspent": true, + "ErgoBoxId": null, + "ErgoCreationHeight": null, + "ErgoRegisters": null, + "ErgoTree": null, + "UtxoTransactionOutputId": 4 + }, + { + "TransactionId": 2, + "AddressId": 64, + "OutputIndex": 2, + "TokenListId": 7, + "IsUnspent": true, + "ErgoBoxId": null, + "ErgoCreationHeight": null, + "ErgoRegisters": null, + "ErgoTree": null, + "UtxoTransactionOutputId": 5 + } + ], + "AccountingTransactionInput": [ + { + "TransactionId": 2, + "AddressId": 64, + "SpendingCounter": 0, + "IndexInOwnTx": 0, + "TokenListId": 4, + "AccountingTransactionInputId": 1 + } + ], + "AccountingTransactionOutput": [], + "TxMemo": [], + "PriceData": [], + "Explorer": [ + { + "ExplorerId": 106, + "NetworkId": 0, + "IsBackup": true, + "Endpoints": { + "address": "https://cardanoscan.io/address/", + "transaction": "https://cardanoscan.io/transaction/", + "pool": "https://cardanoscan.io/pool/", + "stakeAddress": "https://cardanoscan.io/stakeKey/", + "token": "https://cardanoscan.io/token/" + }, + "Name": "CardanoScan" + }, + { + "ExplorerId": 108, + "NetworkId": 0, + "IsBackup": false, + "Endpoints": { + "stakeAddress": "https://adastat.net/addresses/", + "address": "https://adastat.net/addresses/", + "transaction": "https://adastat.net/transactions/", + "pool": "https://adastat.net/pools/" + }, + "Name": "AdaStat" + }, + { + "ExplorerId": 104, + "NetworkId": 0, + "IsBackup": false, + "Endpoints": { + "address": "https://explorer.cardano.org/en/address?address=", + "transaction": "https://explorer.cardano.org/en/transaction?id=" + }, + "Name": "CardanoExplorer" + }, + { + "ExplorerId": 100, + "NetworkId": 0, + "IsBackup": false, + "Endpoints": { + "stakeAddress": "https://adaex.org/", + "address": "https://adaex.org/", + "transaction": "https://adaex.org/", + "pool": "https://adapools.org/pool/" + }, + "Name": "ADAex.org" + }, + { + "ExplorerId": 102, + "NetworkId": 0, + "IsBackup": false, + "Endpoints": { + "address": "https://blockchair.com/cardano/address/", + "transaction": "https://blockchair.com/cardano/transaction/" + }, + "Name": "Blockchair" + }, + { + "ExplorerId": 105, + "NetworkId": 0, + "IsBackup": false, + "Endpoints": { + "stakeAddress": "https://adapools.org/stake/", + "address": "https://adapools.org/address/", + "transaction": "https://adapools.org/transactions/", + "pool": "https://adapools.org/pool/" + }, + "Name": "ADApools" + }, + { + "ExplorerId": 107, + "NetworkId": 0, + "IsBackup": false, + "Endpoints": { + "pool": "https://pooltool.io/pool/" + }, + "Name": "PoolTool" + }, + { + "ExplorerId": 400, + "NetworkId": 300, + "IsBackup": true, + "Endpoints": { + "address": "https://explorer.cardano-testnet.iohkdev.io/en/address?address=", + "transaction": "https://explorer.cardano-testnet.iohkdev.io/en/transaction?id=" + }, + "Name": "CardanoExplorer" + }, + { + "ExplorerId": 200, + "NetworkId": 100, + "IsBackup": true, + "Endpoints": { + "address": "https://adastat.net/address/", + "transaction": "https://adastat.net/transaction/", + "pool": "https://adastat.net/pool/" + }, + "Name": "AdaStat" + }, + { + "ExplorerId": 201, + "NetworkId": 100, + "IsBackup": false, + "Endpoints": { + "address": "https://itnexplorer.cardano.org/en/address/", + "transaction": "https://itnexplorer.cardano.org/en/transaction/" + }, + "Name": "CardanoExplorer" + }, + { + "ExplorerId": 300, + "NetworkId": 200, + "IsBackup": true, + "Endpoints": { + "address": "https://explorer.ergoplatform.com/en/addresses/", + "transaction": "https://explorer.ergoplatform.com/en/transactions/" + }, + "Name": "ErgoPlatform" + }, + { + "ExplorerId": 500, + "NetworkId": 400, + "IsBackup": true, + "Endpoints": { + "address": "https://explorer.alonzo-white.dev.cardano.org/en/address?address=", + "transaction": "https://explorer.alonzo-white.dev.cardano.org/en/transaction?id=" + }, + "Name": "CardanoExplorer" + } + ], + "PreferredExplorer": [] + } +} From ddfab7311cb5ac411284eaa38ba03f771e37bb47 Mon Sep 17 00:00:00 2001 From: yushi Date: Fri, 27 May 2022 16:18:02 +0800 Subject: [PATCH 011/199] associate utxo data with public deriver instead of conceptual wallet --- .../app/api/ada/lib/storage/adaMigration.js | 2 +- .../ada/lib/storage/database/utxo/api/read.js | 16 ++--- .../lib/storage/database/utxo/api/write.js | 28 ++++----- .../ada/lib/storage/database/utxo/tables.js | 38 ++++++------ .../lib/storage/database/utxo/utxo.test.js | 62 +++++++------------ .../lib/storage/models/PublicDeriver/index.js | 2 +- .../storage/models/PublicDeriver/traits.js | 1 + .../lib/storage/tests/adaMigration.test.js | 2 +- 8 files changed, 69 insertions(+), 82 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js b/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js index 99aa6d848a..5be81fcb7e 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js @@ -469,7 +469,7 @@ export async function populateNewUtxodata( tx => ModifyUtxoAtSafePoint.addOrReplace( db, tx, - publicDeriver.getParent().getConceptualWalletId(), + publicDeriver.getPublicDeriverId(), { lastSafeBlockHash: blockHash, utxos: newUtxos diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js index 32eb4f1684..a7bef9b8aa 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js @@ -28,13 +28,13 @@ export class GetUtxoAtSafePoint { static forWallet( db: lf$Database, tx: lf$Transaction, - conceptualWalletId: number, + publicDeriverId: number, ): Promise<$ReadOnly | void> { return getRowFromKey( db, tx, - conceptualWalletId, + publicDeriverId, GetUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].name, - GetUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].properties.ConceptualWalletId, + GetUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].properties.PublicDeriverId, ); } } @@ -51,13 +51,13 @@ export class GetUtxoDiffToBestBlock { static async forWallet( db: lf$Database, tx: lf$Transaction, - conceptualWalletId: number, + publicDeriverId: number, ): Promise> { const rows = await getRowIn( db, tx, GetUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name].name, - GetUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name].properties.ConceptualWalletId, - ([conceptualWalletId]: Array), + GetUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name].properties.PublicDeriverId, + ([publicDeriverId]: Array), ); return rows.map(r => ({ lastBestBlockHash: r.lastBestBlockHash, @@ -70,7 +70,7 @@ export class GetUtxoDiffToBestBlock { static async findLastBestBlockHash( db: lf$Database, tx: lf$Transaction, - conceptualWalletId: number, + publicDeriverId: number, lastBestBlockHash: string, ): Promise<$ReadOnly | void> { const schema = GetUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name]; @@ -81,7 +81,7 @@ export class GetUtxoDiffToBestBlock { .from(table) .where( op.and( - table[schema.properties.ConceptualWalletId].eq(conceptualWalletId), + table[schema.properties.PublicDeriverId].eq(publicDeriverId), table[schema.properties.lastBestBlockHash].eq(lastBestBlockHash) ) ); diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js index fb4832799c..b4e2917ce2 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js @@ -31,14 +31,14 @@ export class ModifyUtxoAtSafePoint { static async addOrReplace( db: lf$Database, tx: lf$Transaction, - conceptualWalletId: number, + publicDeriverId: number, utxoAtSafePoint: UtxoAtSafePoint ): Promise { - const row = await GetUtxoAtSafePoint.forWallet(db, tx, conceptualWalletId); + const row = await GetUtxoAtSafePoint.forWallet(db, tx, publicDeriverId); if (row) { const newRow: UtxoAtSafePointRow = { UtxoAtSafePointId: row.UtxoAtSafePointId, - ConceptualWalletId: conceptualWalletId, + PublicDeriverId: publicDeriverId, UtxoAtSafePoint: utxoAtSafePoint, }; await addOrReplaceRow( @@ -50,7 +50,7 @@ export class ModifyUtxoAtSafePoint { await addNewRowToTable( db, tx, { - ConceptualWalletId: conceptualWalletId, + PublicDeriverId: publicDeriverId, UtxoAtSafePoint: utxoAtSafePoint, }, ModifyUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].name, @@ -61,13 +61,13 @@ export class ModifyUtxoAtSafePoint { static async remove( db: lf$Database, tx: lf$Transaction, - conceptualWalletId: number, + publicDeriverId: number, ): Promise { await removeFromTableBatch( db, tx, ModifyUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].name, - ModifyUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].properties.ConceptualWalletId, - ([conceptualWalletId]: Array), + ModifyUtxoAtSafePoint.ownTables[Tables.UtxoAtSafePointSchema.name].properties.PublicDeriverId, + ([publicDeriverId]: Array), ); } } @@ -84,7 +84,7 @@ export class ModifyUtxoDiffToBestBlock { static async removeAll( db: lf$Database, tx: lf$Transaction, - conceptualWalletId: number, + publicDeriverId: number, ): Promise { const schema = ModifyUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name]; const tableName = schema.name; @@ -92,14 +92,14 @@ export class ModifyUtxoDiffToBestBlock { const table = db.getSchema().table(tableName); await tx.attach( db.delete().from(table) - .where(table[fieldNames.ConceptualWalletId].eq(conceptualWalletId)) + .where(table[fieldNames.PublicDeriverId].eq(publicDeriverId)) ); } static async remove( db: lf$Database, tx: lf$Transaction, - conceptualWalletId: number, + publicDeriverId: number, lastBestBlockHash: string, ): Promise { const schema = ModifyUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name]; @@ -110,7 +110,7 @@ export class ModifyUtxoDiffToBestBlock { db.delete().from(table) .where( op.and( - table[fieldNames.ConceptualWalletId].eq(conceptualWalletId), + table[fieldNames.PublicDeriverId].eq(publicDeriverId), table[fieldNames.lastBestBlockHash].eq(lastBestBlockHash) ) ) @@ -120,7 +120,7 @@ export class ModifyUtxoDiffToBestBlock { static async add( db: lf$Database, tx: lf$Transaction, - conceptualWalletId: number, + publicDeriverId: number, utxoDiffToBestBlock: UtxoDiffToBestBlock, ): Promise { // Do nothing if a row with `utxoDiffToBestBlock.lastBestBlockHash` is already @@ -129,7 +129,7 @@ export class ModifyUtxoDiffToBestBlock { // only for this query. const existing = await GetUtxoDiffToBestBlock.findLastBestBlockHash( db, tx, - conceptualWalletId, + publicDeriverId, utxoDiffToBestBlock.lastBestBlockHash ); @@ -137,7 +137,7 @@ export class ModifyUtxoDiffToBestBlock { await addNewRowToTable( db, tx, { - ConceptualWalletId: conceptualWalletId, + PublicDeriverId: publicDeriverId, lastBestBlockHash: utxoDiffToBestBlock.lastBestBlockHash, spentUtxoIds: utxoDiffToBestBlock.spentUtxoIds, newUtxos: utxoDiffToBestBlock.newUtxos, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js index 23d3b2804d..8e8f418bf1 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js'; import { Type, ConstraintAction, } from 'lovefield'; import type { lf$schema$Builder } from 'lovefield'; -import { ConceptualWalletSchema } from '../walletTypes/core/tables'; +import { PublicDeriverSchema } from '../walletTypes/core/tables'; type Asset = {| assetId: string, @@ -34,7 +34,7 @@ export type UtxoDiffToBestBlock = {| // DB schema: export type UtxoAtSafePointInsert = {| - ConceptualWalletId: number, + PublicDeriverId: number, UtxoAtSafePoint: UtxoAtSafePoint, |}; export type UtxoAtSafePointRow = {| @@ -48,13 +48,13 @@ export const UtxoAtSafePointSchema: {| name: 'UtxoAtSafePointTable', properties: { UtxoAtSafePointId: 'UtxoAtSafePointId', - ConceptualWalletId: 'ConceptualWalletId', + PublicDeriverId: 'PublicDeriverId', UtxoAtSafePoint: 'UtxoAtSafePoint', }, }; export type UtxoDiffToBestBlockInsert = {| - ConceptualWalletId: number, + PublicDeriverId: number, // we need to index into the `lastBestBlockHash` field, so we have to spread it ...UtxoDiffToBestBlock, |}; @@ -69,7 +69,7 @@ export const UtxoDiffToBestBlockSchema: {| name: 'UtxoDiffToBestBlock', properties: { UtxoDiffToBestBlockId: 'UtxoDiffToBestBlockId', - ConceptualWalletId: 'ConceptualWalletId', + PublicDeriverId: 'PublicDeriverId', lastBestBlockHash: 'lastBestBlockHash', spentUtxoIds: 'spentUtxoIds', newUtxos: 'newUtxos', @@ -79,25 +79,25 @@ export const UtxoDiffToBestBlockSchema: {| export const populateUtxoDb = (schemaBuilder: lf$schema$Builder) => { schemaBuilder.createTable(UtxoAtSafePointSchema.name) .addColumn(UtxoAtSafePointSchema.properties.UtxoAtSafePointId, Type.INTEGER) - .addColumn(UtxoAtSafePointSchema.properties.ConceptualWalletId, Type.INTEGER) + .addColumn(UtxoAtSafePointSchema.properties.PublicDeriverId, Type.INTEGER) .addColumn(UtxoAtSafePointSchema.properties.UtxoAtSafePoint, Type.OBJECT) .addPrimaryKey( ([UtxoAtSafePointSchema.properties.UtxoAtSafePointId]: Array), true ) - .addForeignKey('UtxoAtSafePoint_ConceptualWallet', { - local: UtxoAtSafePointSchema.properties.ConceptualWalletId, - ref: `${ConceptualWalletSchema.name}.${ConceptualWalletSchema.properties.ConceptualWalletId}` + .addForeignKey('UtxoAtSafePoint_PublicDeriver', { + local: UtxoAtSafePointSchema.properties.PublicDeriverId, + ref: `${PublicDeriverSchema.name}.${PublicDeriverSchema.properties.PublicDeriverId}` }) .addIndex( - 'UtxoAtSafePoint_ConceptualWallet_Index', - ([UtxoAtSafePointSchema.properties.ConceptualWalletId]: Array), + 'UtxoAtSafePoint_PublicDeriver_Index', + ([UtxoAtSafePointSchema.properties.PublicDeriverId]: Array), false ); schemaBuilder.createTable(UtxoDiffToBestBlockSchema.name) .addColumn(UtxoDiffToBestBlockSchema.properties.UtxoDiffToBestBlockId, Type.INTEGER) - .addColumn(UtxoDiffToBestBlockSchema.properties.ConceptualWalletId, Type.INTEGER) + .addColumn(UtxoDiffToBestBlockSchema.properties.PublicDeriverId, Type.INTEGER) .addColumn(UtxoDiffToBestBlockSchema.properties.lastBestBlockHash, Type.STRING) .addColumn(UtxoDiffToBestBlockSchema.properties.spentUtxoIds, Type.OBJECT) .addColumn(UtxoDiffToBestBlockSchema.properties.newUtxos, Type.OBJECT) @@ -105,20 +105,20 @@ export const populateUtxoDb = (schemaBuilder: lf$schema$Builder) => { ([UtxoDiffToBestBlockSchema.properties.UtxoDiffToBestBlockId]: Array), true ) - .addForeignKey('UtxoDiffToBestBlock_ConceptualWallet', { - local: UtxoDiffToBestBlockSchema.properties.ConceptualWalletId, - ref: `${ConceptualWalletSchema.name}.${ConceptualWalletSchema.properties.ConceptualWalletId}` + .addForeignKey('UtxoDiffToBestBlock_PublicDeriver', { + local: UtxoDiffToBestBlockSchema.properties.PublicDeriverId, + ref: `${PublicDeriverSchema.name}.${PublicDeriverSchema.properties.PublicDeriverId}` }) .addIndex( - 'UtxoDiffToBestBlock_ConceptualWallet_Index', - ([UtxoDiffToBestBlockSchema.properties.ConceptualWalletId]: Array), + 'UtxoDiffToBestBlock_PublicDeriver_Index', + ([UtxoDiffToBestBlockSchema.properties.PublicDeriverId]: Array), false ) .addIndex( - 'UtxoDiffToBestBlock_ConceptualWallet_lastBestBlockHash_Index', + 'UtxoDiffToBestBlock_PublicDeriver_lastBestBlockHash_Index', ( [ - UtxoDiffToBestBlockSchema.properties.ConceptualWalletId, + UtxoDiffToBestBlockSchema.properties.PublicDeriverId, UtxoDiffToBestBlockSchema.properties.lastBestBlockHash, ]: Array ), diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js index 52f6310492..38c88947af 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js @@ -1,16 +1,11 @@ // @flow import { schema, } from 'lovefield'; -import { loadLovefieldDB } from '../index'; +import { loadLovefieldDBFromDump } from '../index'; import { GetUtxoAtSafePoint, GetUtxoDiffToBestBlock } from './api/read'; -import { ModifyConceptualWallet } from '../walletTypes/core/api/write'; import { ModifyUtxoAtSafePoint, ModifyUtxoDiffToBestBlock } from './api/write'; import { getAllSchemaTables, raii } from '../utils'; - -const CONCEPTUAL_WALLET = { - Name: '', - NetworkId: 1, -}; +import dbdump from '../../tests/testDb.dump.json'; const UTXO_AT_SAFE_BLOCK_1 = { lastSafeBlockHash: 'lastSafeBlockHash1', @@ -75,21 +70,12 @@ const UTXO_DIFF_TO_BEST_BLOCK_2 = { }; let db; -let conceptualWalletId; +let publicDeriverId; beforeAll(async () => { - db = await loadLovefieldDB(schema.DataStoreType.MEMORY); + db = await loadLovefieldDBFromDump(schema.DataStoreType.MEMORY, dbdump); - const result = await raii( - db, - getAllSchemaTables(db, ModifyConceptualWallet), - async tx => ModifyConceptualWallet.add( - db, - tx, - CONCEPTUAL_WALLET, - ) - ); - conceptualWalletId = result.ConceptualWalletId; + publicDeriverId = 1; }); test('UtxoAtSafePoint', async () => { @@ -101,7 +87,7 @@ test('UtxoAtSafePoint', async () => { await ModifyUtxoAtSafePoint.addOrReplace( db, tx, - conceptualWalletId, + publicDeriverId, UTXO_AT_SAFE_BLOCK_1, ); } @@ -114,7 +100,7 @@ test('UtxoAtSafePoint', async () => { tx => GetUtxoAtSafePoint.forWallet( db, tx, - conceptualWalletId, + publicDeriverId, ) ); @@ -130,7 +116,7 @@ test('UtxoAtSafePoint', async () => { await ModifyUtxoAtSafePoint.addOrReplace( db, tx, - conceptualWalletId, + publicDeriverId, UTXO_AT_SAFE_BLOCK_2, ); } @@ -143,7 +129,7 @@ test('UtxoAtSafePoint', async () => { tx => GetUtxoAtSafePoint.forWallet( db, tx, - conceptualWalletId, + publicDeriverId, ) ); @@ -159,7 +145,7 @@ test('UtxoAtSafePoint', async () => { await ModifyUtxoAtSafePoint.remove( db, tx, - conceptualWalletId, + publicDeriverId, ); } ); @@ -171,7 +157,7 @@ test('UtxoAtSafePoint', async () => { tx => GetUtxoAtSafePoint.forWallet( db, tx, - conceptualWalletId, + publicDeriverId, ) ); @@ -186,7 +172,7 @@ test('UtxoDiffToBestBlock', async () => { tx => GetUtxoDiffToBestBlock.forWallet( db, tx, - conceptualWalletId, + publicDeriverId, ) ); @@ -200,7 +186,7 @@ test('UtxoDiffToBestBlock', async () => { await ModifyUtxoDiffToBestBlock.add( db, tx, - conceptualWalletId, + publicDeriverId, UTXO_DIFF_TO_BEST_BLOCK_1, ); } @@ -213,7 +199,7 @@ test('UtxoDiffToBestBlock', async () => { tx => GetUtxoDiffToBestBlock.forWallet( db, tx, - conceptualWalletId, + publicDeriverId, ) ); @@ -225,7 +211,7 @@ test('UtxoDiffToBestBlock', async () => { tx => GetUtxoDiffToBestBlock.findLastBestBlockHash( db, tx, - conceptualWalletId, + publicDeriverId, UTXO_DIFF_TO_BEST_BLOCK_1.lastBestBlockHash, ) ); @@ -240,7 +226,7 @@ test('UtxoDiffToBestBlock', async () => { await ModifyUtxoDiffToBestBlock.add( db, tx, - conceptualWalletId, + publicDeriverId, UTXO_DIFF_TO_BEST_BLOCK_1, ); } @@ -253,7 +239,7 @@ test('UtxoDiffToBestBlock', async () => { tx => GetUtxoDiffToBestBlock.forWallet( db, tx, - conceptualWalletId, + publicDeriverId, ) ); @@ -267,7 +253,7 @@ test('UtxoDiffToBestBlock', async () => { await ModifyUtxoDiffToBestBlock.remove( db, tx, - conceptualWalletId, + publicDeriverId, UTXO_DIFF_TO_BEST_BLOCK_1.lastBestBlockHash, ); } @@ -280,7 +266,7 @@ test('UtxoDiffToBestBlock', async () => { tx => GetUtxoDiffToBestBlock.forWallet( db, tx, - conceptualWalletId, + publicDeriverId, ) ); @@ -294,7 +280,7 @@ test('UtxoDiffToBestBlock', async () => { await ModifyUtxoDiffToBestBlock.add( db, tx, - conceptualWalletId, + publicDeriverId, UTXO_DIFF_TO_BEST_BLOCK_1, ); } @@ -306,7 +292,7 @@ test('UtxoDiffToBestBlock', async () => { await ModifyUtxoDiffToBestBlock.add( db, tx, - conceptualWalletId, + publicDeriverId, UTXO_DIFF_TO_BEST_BLOCK_2, ); } @@ -319,7 +305,7 @@ test('UtxoDiffToBestBlock', async () => { tx => GetUtxoDiffToBestBlock.forWallet( db, tx, - conceptualWalletId, + publicDeriverId, ) ); @@ -333,7 +319,7 @@ test('UtxoDiffToBestBlock', async () => { await ModifyUtxoDiffToBestBlock.removeAll( db, tx, - conceptualWalletId, + publicDeriverId, ); } ); @@ -345,7 +331,7 @@ test('UtxoDiffToBestBlock', async () => { tx => GetUtxoDiffToBestBlock.forWallet( db, tx, - conceptualWalletId, + publicDeriverId, ) ); diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js index 11ccf7b03a..4ce3a975ce 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js @@ -72,7 +72,7 @@ implements IPublicDeriver, IRename, IGetLastSyncInfo { throw new Error('missing backend service URL'); } const utxoApi = new UtxoApi(BackendService); - this.utxoStorageApi = new UtxoStorageApi(this.parent.getConceptualWalletId()); + this.utxoStorageApi = new UtxoStorageApi(this.publicDeriverId); this.utxoService = new UtxoService(utxoApi, this.utxoStorageApi); return this; diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js index 46c0998b90..d4e8cc201f 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js @@ -304,6 +304,7 @@ const GetAllUtxosMixin = ( address: addressingInfo.address, }; }); + return addressedUtxos; } // Ergo: return this.rawGetAllUtxosFromOldDb(tx, deps, _body, derivationTables); diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/tests/adaMigration.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/tests/adaMigration.test.js index 913ef17623..95225a4f13 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/tests/adaMigration.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/tests/adaMigration.test.js @@ -90,7 +90,7 @@ test('Migrate to Yoroi-lib UtxoService storage', async () => { expect(dump.tables.UtxoAtSafePointTable).toEqual( [ { - ConceptualWalletId: 1, + PublicDeriverId: 1, UtxoAtSafePoint: { lastSafeBlockHash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba26', utxos: [ From 990a757705dea8fde5d9e13d5ff18c65f4686a43 Mon Sep 17 00:00:00 2001 From: yushi Date: Fri, 27 May 2022 18:32:34 +0800 Subject: [PATCH 012/199] fix rollback handling and tests --- .../api/ada/lib/state-fetch/mockNetwork.js | 70 +++-- .../tests/__snapshots__/status.test.js.snap | 92 ++++--- .../storage/bridge/tests/multiwallet.test.js | 48 +--- .../lib/storage/bridge/tests/shelley.test.js | 72 +---- .../storage/bridge/tests/simpleTxs.test.js | 245 +++++++----------- .../lib/storage/bridge/tests/status.test.js | 100 ++++++- .../lib/storage/bridge/updateTransactions.js | 161 +++++++----- .../tests/__snapshots__/index.test.js.snap | 2 + packages/yoroi-extension/package-lock.json | 139 +++++++++- packages/yoroi-extension/package.json | 2 +- 10 files changed, 548 insertions(+), 383 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js index ee2fcea27b..428bfe2015 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js @@ -696,45 +696,59 @@ export function genGetMultiAssetMetadata( export class MockUtxoApi implements UtxoApiContract { blockchain: Array; - lastSafeBlockTxIndex: number; + safeConfirmations: number; constructor( blockchain: Array, safeConfirmations: number, ) { this.blockchain = blockchain; - - let lastHeight = blockchain[blockchain.length - 1].height; - if (lastHeight == null) { - throw new Error('missing height'); - } + this.safeConfirmations = safeConfirmations; + } + + _getLastSafeBlockTxIndex(): number { + let lastHeight = null; let i; - for (i = blockchain.length - 1; i >= 0; i --) { - const currentHeight = blockchain[i].height; - if (currentHeight == null) { - throw new Error('missing height'); + for (i = this.blockchain.length - 1; i >= 0; i --) { + if (this.blockchain[i].tx_state === 'Successful') { + lastHeight = this.blockchain[i].height; + break; } - if (lastHeight - currentHeight >= safeConfirmations) { + } + if (lastHeight == null) { + throw new Error('no successful tx'); + } + for (; i >= 0; i --) { + const currentHeight = this.blockchain[i].height; + if ( + currentHeight != null && + lastHeight - currentHeight >= this.safeConfirmations + ) { break; } } if (i === -1) { throw new Error('not enough blocks for a safe block'); } else { - this.lastSafeBlockTxIndex = i; + return i; } } async getBestBlock(): Promise { - const hash = this.blockchain[this.blockchain.length - 1].block_hash; - if (!hash) { - throw new Error('expect hash'); + for (let i = this.blockchain.length - 1; i >= 0; i --) { + if (this.blockchain[i].tx_state === 'Successful') { + const hash = this.blockchain[i].block_hash; + if (!hash) { + throw new Error('expect hash'); + } + return hash; + } } - return hash; + throw new Error('no successful tx'); } async getSafeBlock(): Promise { - const hash = this.blockchain[this.lastSafeBlockTxIndex].block_hash; + const hash = this.blockchain[this._getLastSafeBlockTxIndex()].block_hash; if (!hash) { throw new Error('expect hash'); } @@ -786,6 +800,9 @@ export class MockUtxoApi implements UtxoApiContract { let utxos = []; for (let i = 0; i <= lastTxIndex; i++) { const tx = this.blockchain[i]; + if (tx.tx_state !== 'Successful') { + continue; + } // remove spent utxos = utxos.filter( utxo => !tx.inputs.some( @@ -793,9 +810,11 @@ export class MockUtxoApi implements UtxoApiContract { ) ); // add new - tx.outputs.filter( - ({ address }) => addresses.includes(address) - ).forEach((output, outputIndex) => { + tx.outputs.forEach((output, outputIndex) => { + if (!addresses.includes(output.address)) { + return; + } + const { height } = tx; if (height == null) { throw new Error('expect height'); @@ -829,6 +848,10 @@ export class MockUtxoApi implements UtxoApiContract { let utxoDiffItems = []; for (let i = this.blockchain.length - 1; i >= 0; i--) { const tx = this.blockchain[i]; + if (tx.tx_state !== 'Successful') { + continue; + } + if (tx.block_hash === untilBlockHash) { seenUntilBlock = true; } @@ -838,9 +861,10 @@ export class MockUtxoApi implements UtxoApiContract { break; } - tx.outputs.filter( - ({ address }) => addresses.includes(address) - ).forEach((output, outputIndex) => { + tx.outputs.forEach((output, outputIndex) => { + if (!addresses.includes(output.address)) { + return; + } const utxoId = `${tx.hash}${outputIndex}` utxoDiffItems.push( { diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/__snapshots__/status.test.js.snap b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/__snapshots__/status.test.js.snap index 1c7f69bb4a..528a6e64b0 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/__snapshots__/status.test.js.snap +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/__snapshots__/status.test.js.snap @@ -32,7 +32,7 @@ exports[`Syncing with failed bip44 1`] = ` \\"utxoOutputs\\": [ { \\"TransactionId\\": 3, - \\"AddressId\\": 48, + \\"AddressId\\": 50, \\"OutputIndex\\": 0, \\"IsUnspent\\": true, \\"ErgoBoxId\\": null, @@ -195,7 +195,7 @@ exports[`Syncing with failed bip44 1`] = ` \\"utxoInputs\\": [ { \\"TransactionId\\": 1, - \\"AddressId\\": 46, + \\"AddressId\\": 41, \\"ParentTxHash\\": \\"9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d20\\", \\"IndexInParentTx\\": 0, \\"IndexInOwnTx\\": 0, @@ -218,7 +218,7 @@ exports[`Syncing with failed bip44 1`] = ` }, { \\"TransactionId\\": 1, - \\"AddressId\\": 47, + \\"AddressId\\": 42, \\"OutputIndex\\": 1, \\"IsUnspent\\": true, \\"ErgoBoxId\\": null, @@ -388,7 +388,7 @@ exports[`Syncing with failed bip44 1`] = ` \\"utxoInputs\\": [ { \\"TransactionId\\": 2, - \\"AddressId\\": 46, + \\"AddressId\\": 48, \\"ParentTxHash\\": \\"9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d21\\", \\"IndexInParentTx\\": 0, \\"IndexInOwnTx\\": 0, @@ -411,7 +411,7 @@ exports[`Syncing with failed bip44 1`] = ` }, { \\"TransactionId\\": 2, - \\"AddressId\\": 47, + \\"AddressId\\": 49, \\"OutputIndex\\": 1, \\"IsUnspent\\": true, \\"ErgoBoxId\\": null, @@ -814,48 +814,60 @@ Array [ }, Object { "AddressId": 41, + "Digest": -1.2688708748746819e-61, + "Hash": "Ae2tdPwUPEZ5PxKxoyZDgjsKgMWMpTRa4PH3sVgARSGBsWwNBH3qg7cMFsP", + "Type": 0, + }, + Object { + "AddressId": 42, + "Digest": 3.0263387081779602e-151, + "Hash": "Ae2tdPwUPEZE9RAm3d3zuuh22YjqDxhR1JF6G93uJsRrk51QGHzRUzLvDjL", + "Type": 0, + }, + Object { + "AddressId": 43, "Digest": -1.3416871383074957e-161, "Hash": "Ae2tdPwUPEYxD5EgazsN7DcLp2fFiak1RTco6CoDNioKVuodftS4oNeabwz", "Type": 0, }, Object { - "AddressId": 42, + "AddressId": 44, "Digest": 2.414293239739638e-47, "Hash": "Ae2tdPwUPEZFqcMbS757FyZrABDZUVtzEW4sPvzZj3UYBcn8BSWFBxmQK2n", "Type": 0, }, Object { - "AddressId": 43, + "AddressId": 45, "Digest": 1.1036862858872953e+216, "Hash": "Ae2tdPwUPEZGR3uPidVc5p1cSnV9KDzDxrrembeFhLYMgFAa4KHpTqmncFQ", "Type": 0, }, Object { - "AddressId": 44, + "AddressId": 46, "Digest": -2.938948476649332e-97, "Hash": "Ae2tdPwUPEZEvn7nMBBaBMCc773zeMCPsS93tDsFvyZw4quRbnq5bnP1i8F", "Type": 0, }, Object { - "AddressId": 45, + "AddressId": 47, "Digest": 8.159203653530658e-92, "Hash": "Ae2tdPwUPEZAzQ7FdZ8ksAjK7iVGzjMKUxWqkQLF52oGxWBNfyDRocqJwBT", "Type": 0, }, Object { - "AddressId": 46, + "AddressId": 48, "Digest": -1.2688708748746819e-61, "Hash": "Ae2tdPwUPEZ5PxKxoyZDgjsKgMWMpTRa4PH3sVgARSGBsWwNBH3qg7cMFsP", "Type": 0, }, Object { - "AddressId": 47, + "AddressId": 49, "Digest": 3.0263387081779602e-151, "Hash": "Ae2tdPwUPEZE9RAm3d3zuuh22YjqDxhR1JF6G93uJsRrk51QGHzRUzLvDjL", "Type": 0, }, Object { - "AddressId": 48, + "AddressId": 50, "Digest": 3.0263387081779602e-151, "Hash": "Ae2tdPwUPEZE9RAm3d3zuuh22YjqDxhR1JF6G93uJsRrk51QGHzRUzLvDjL", "Type": 0, @@ -1073,7 +1085,7 @@ Array [ Object { "UtxoTransactionInput": Array [ Object { - "AddressId": 46, + "AddressId": 41, "IndexInOwnTx": 0, "IndexInParentTx": 0, "ParentTxHash": "9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d20", @@ -1082,7 +1094,7 @@ Array [ "UtxoTransactionInputId": 1, }, Object { - "AddressId": 46, + "AddressId": 48, "IndexInOwnTx": 0, "IndexInParentTx": 0, "ParentTxHash": "9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d21", @@ -1116,7 +1128,7 @@ Array [ "UtxoTransactionOutputId": 1, }, Object { - "AddressId": 47, + "AddressId": 42, "ErgoBoxId": null, "ErgoCreationHeight": null, "ErgoRegisters": null, @@ -1140,7 +1152,7 @@ Array [ "UtxoTransactionOutputId": 3, }, Object { - "AddressId": 47, + "AddressId": 49, "ErgoBoxId": null, "ErgoCreationHeight": null, "ErgoRegisters": null, @@ -1152,7 +1164,7 @@ Array [ "UtxoTransactionOutputId": 4, }, Object { - "AddressId": 48, + "AddressId": 50, "ErgoBoxId": null, "ErgoCreationHeight": null, "ErgoRegisters": null, @@ -1200,7 +1212,7 @@ exports[`Syncing with pending bip44 1`] = ` \\"utxoOutputs\\": [ { \\"TransactionId\\": 3, - \\"AddressId\\": 48, + \\"AddressId\\": 50, \\"OutputIndex\\": 0, \\"IsUnspent\\": true, \\"ErgoBoxId\\": null, @@ -1363,7 +1375,7 @@ exports[`Syncing with pending bip44 1`] = ` \\"utxoInputs\\": [ { \\"TransactionId\\": 1, - \\"AddressId\\": 46, + \\"AddressId\\": 41, \\"ParentTxHash\\": \\"9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d20\\", \\"IndexInParentTx\\": 0, \\"IndexInOwnTx\\": 0, @@ -1386,7 +1398,7 @@ exports[`Syncing with pending bip44 1`] = ` }, { \\"TransactionId\\": 1, - \\"AddressId\\": 47, + \\"AddressId\\": 42, \\"OutputIndex\\": 1, \\"IsUnspent\\": true, \\"ErgoBoxId\\": null, @@ -1556,7 +1568,7 @@ exports[`Syncing with pending bip44 1`] = ` \\"utxoInputs\\": [ { \\"TransactionId\\": 2, - \\"AddressId\\": 46, + \\"AddressId\\": 48, \\"ParentTxHash\\": \\"9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d21\\", \\"IndexInParentTx\\": 0, \\"IndexInOwnTx\\": 0, @@ -1579,7 +1591,7 @@ exports[`Syncing with pending bip44 1`] = ` }, { \\"TransactionId\\": 2, - \\"AddressId\\": 47, + \\"AddressId\\": 49, \\"OutputIndex\\": 1, \\"IsUnspent\\": true, \\"ErgoBoxId\\": null, @@ -1982,48 +1994,60 @@ Array [ }, Object { "AddressId": 41, + "Digest": -1.2688708748746819e-61, + "Hash": "Ae2tdPwUPEZ5PxKxoyZDgjsKgMWMpTRa4PH3sVgARSGBsWwNBH3qg7cMFsP", + "Type": 0, + }, + Object { + "AddressId": 42, + "Digest": 3.0263387081779602e-151, + "Hash": "Ae2tdPwUPEZE9RAm3d3zuuh22YjqDxhR1JF6G93uJsRrk51QGHzRUzLvDjL", + "Type": 0, + }, + Object { + "AddressId": 43, "Digest": -1.3416871383074957e-161, "Hash": "Ae2tdPwUPEYxD5EgazsN7DcLp2fFiak1RTco6CoDNioKVuodftS4oNeabwz", "Type": 0, }, Object { - "AddressId": 42, + "AddressId": 44, "Digest": 2.414293239739638e-47, "Hash": "Ae2tdPwUPEZFqcMbS757FyZrABDZUVtzEW4sPvzZj3UYBcn8BSWFBxmQK2n", "Type": 0, }, Object { - "AddressId": 43, + "AddressId": 45, "Digest": 1.1036862858872953e+216, "Hash": "Ae2tdPwUPEZGR3uPidVc5p1cSnV9KDzDxrrembeFhLYMgFAa4KHpTqmncFQ", "Type": 0, }, Object { - "AddressId": 44, + "AddressId": 46, "Digest": -2.938948476649332e-97, "Hash": "Ae2tdPwUPEZEvn7nMBBaBMCc773zeMCPsS93tDsFvyZw4quRbnq5bnP1i8F", "Type": 0, }, Object { - "AddressId": 45, + "AddressId": 47, "Digest": 8.159203653530658e-92, "Hash": "Ae2tdPwUPEZAzQ7FdZ8ksAjK7iVGzjMKUxWqkQLF52oGxWBNfyDRocqJwBT", "Type": 0, }, Object { - "AddressId": 46, + "AddressId": 48, "Digest": -1.2688708748746819e-61, "Hash": "Ae2tdPwUPEZ5PxKxoyZDgjsKgMWMpTRa4PH3sVgARSGBsWwNBH3qg7cMFsP", "Type": 0, }, Object { - "AddressId": 47, + "AddressId": 49, "Digest": 3.0263387081779602e-151, "Hash": "Ae2tdPwUPEZE9RAm3d3zuuh22YjqDxhR1JF6G93uJsRrk51QGHzRUzLvDjL", "Type": 0, }, Object { - "AddressId": 48, + "AddressId": 50, "Digest": 3.0263387081779602e-151, "Hash": "Ae2tdPwUPEZE9RAm3d3zuuh22YjqDxhR1JF6G93uJsRrk51QGHzRUzLvDjL", "Type": 0, @@ -2241,7 +2265,7 @@ Array [ Object { "UtxoTransactionInput": Array [ Object { - "AddressId": 46, + "AddressId": 41, "IndexInOwnTx": 0, "IndexInParentTx": 0, "ParentTxHash": "9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d20", @@ -2250,7 +2274,7 @@ Array [ "UtxoTransactionInputId": 1, }, Object { - "AddressId": 46, + "AddressId": 48, "IndexInOwnTx": 0, "IndexInParentTx": 0, "ParentTxHash": "9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d21", @@ -2284,7 +2308,7 @@ Array [ "UtxoTransactionOutputId": 1, }, Object { - "AddressId": 47, + "AddressId": 42, "ErgoBoxId": null, "ErgoCreationHeight": null, "ErgoRegisters": null, @@ -2308,7 +2332,7 @@ Array [ "UtxoTransactionOutputId": 3, }, Object { - "AddressId": 47, + "AddressId": 49, "ErgoBoxId": null, "ErgoCreationHeight": null, "ErgoRegisters": null, @@ -2320,7 +2344,7 @@ Array [ "UtxoTransactionOutputId": 4, }, Object { - "AddressId": 48, + "AddressId": 50, "ErgoBoxId": null, "ErgoCreationHeight": null, "ErgoRegisters": null, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js index 0470e183d3..739360fa5e 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js @@ -28,7 +28,8 @@ import { genGetBestBlock, getSingleAddressString, genGetTokenInfo, - genGetMultiAssetMetadata + genGetMultiAssetMetadata, + MockUtxoApi, } from '../../../state-fetch/mockNetwork'; import { loadLovefieldDB } from '../../database/index'; @@ -49,6 +50,8 @@ import { networks, } from '../../database/prepackaged/networks'; import { TransactionType } from '../../database/primitives/tables'; +import UtxoApi from '../../../state-fetch/utxoApi'; +import { RustModule } from '../../../cardanoCrypto/rustLoader'; jest.mock('../../database/initialSeed'); @@ -168,6 +171,10 @@ const networkTransactions: number => Array = (purpose) => [{ ] }]; +beforeAll(async () => { + await RustModule.load(); +}); + beforeEach(() => { mockDate(); }); @@ -208,28 +215,14 @@ async function checkPub1HasTx( }, output: { Transaction: { - Type: TransactionType.CardanoByron, - ErrorMessage: null, Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545', - Digest: 8.191593645542673e-27, - Ordinal: 0, - BlockId: 1, - LastUpdateTime: 1568392636000, - Status: 1, - TransactionId: 1, - Extra: null, }, UtxoTransactionOutput: { - AddressId: 5, - IsUnspent: true, OutputIndex: 0, - TransactionId: 1, - UtxoTransactionOutputId: 1, ErgoBoxId: null, ErgoCreationHeight: null, ErgoRegisters: null, ErgoTree: null, - TokenListId: 1, }, tokens: [{ Token: { @@ -250,9 +243,6 @@ async function checkPub1HasTx( }, TokenList: { Amount: '2100000', - ListId: 1, - TokenId: 1, - TokenListItemId: 2, }, }], } @@ -344,28 +334,14 @@ async function checkPub2HasTx( }, output: { Transaction: { - Type: TransactionType.CardanoByron, - ErrorMessage: null, Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545', - Digest: 8.191593645542673e-27, - Ordinal: 0, - BlockId: 1, - LastUpdateTime: 1568392636000, - Status: 1, - TransactionId: 2, - Extra: null, }, UtxoTransactionOutput: { - AddressId: 41, - IsUnspent: true, OutputIndex: 1, - TransactionId: 2, - UtxoTransactionOutputId: 4, ErgoBoxId: null, ErgoCreationHeight: null, ErgoRegisters: null, ErgoTree: null, - TokenListId: 5, }, tokens: [{ Token: { @@ -386,9 +362,6 @@ async function checkPub2HasTx( }, TokenList: { Amount: '2700000', - ListId: 5, - TokenId: 1, - TokenListItemId: 6, }, }], } @@ -414,11 +387,13 @@ async function checkPub2HasTx( async function syncingSimpleTransaction( purposeForTest: WalletTypePurposeT, ): Promise { + const txHistory = networkTransactions(purposeForTest); + UtxoApi.utxoApiFactory = (_: string) => new MockUtxoApi(txHistory, 0); + const db = await loadLovefieldDB(schema.DataStoreType.MEMORY); const publicDeriver1 = await setup(db, TX_TEST_MNEMONIC_1, purposeForTest); const publicDeriver2 = await setup(db, TX_TEST_MNEMONIC_2, purposeForTest); - const txHistory = networkTransactions(purposeForTest); const network = networks.CardanoMainnet; const checkAddressesInUse = genCheckAddressesInUse(txHistory, network); const getTransactionsHistoryForAddresses = genGetTransactionsHistoryForAddresses( @@ -491,7 +466,6 @@ async function syncingSimpleTransaction( getTokenInfo, getMultiAssetMetadata ); - await checkPub2HasTx(purposeForTest, publicDeriver2); { const response = await publicDeriver2.getLastSyncInfo(); diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js index 0e7227f0e1..edc94a93e1 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js @@ -21,7 +21,8 @@ import { genGetBestBlock, getSingleAddressString, genGetTokenInfo, - genGetMultiAssetMetadata + genGetMultiAssetMetadata, + MockUtxoApi, } from '../../../state-fetch/mockNetwork'; import { HARD_DERIVATION_START, @@ -45,6 +46,8 @@ import { networks, } from '../../database/prepackaged/networks'; import { TransactionType } from '../../database/primitives/tables'; +import UtxoApi from '../../../state-fetch/utxoApi'; +import { RustModule } from '../../../cardanoCrypto/rustLoader'; jest.mock('../../database/initialSeed'); @@ -200,6 +203,10 @@ const nextRegularSpend: number => RemoteTransaction = (purpose) => ({ metadata: null, }); +beforeAll(async () => { + await RustModule.load(); +}); + beforeEach(() => { mockDate(); }); @@ -207,11 +214,13 @@ beforeEach(() => { async function syncingSimpleTransaction( purposeForTest: WalletTypePurposeT, ): Promise { + const txHistory = networkTransactions(purposeForTest); + UtxoApi.utxoApiFactory = (_: string) => new MockUtxoApi(txHistory, 0); + const db = await loadLovefieldDB(schema.DataStoreType.MEMORY); const publicDeriver = await setup(db, TX_TEST_MNEMONIC_1, purposeForTest); const network = networks.CardanoMainnet; - const txHistory = networkTransactions(purposeForTest); const checkAddressesInUse = genCheckAddressesInUse(txHistory, network); const getTransactionsHistoryForAddresses = genGetTransactionsHistoryForAddresses( txHistory, @@ -266,28 +275,14 @@ async function syncingSimpleTransaction( }, output: { Transaction: { - Type: TransactionType.CardanoByron, - ErrorMessage: null, Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545', - Digest: 8.191593645542673e-27, - Ordinal: 0, - BlockId: 1, - LastUpdateTime: 1568392636000, - Status: 1, - TransactionId: 1, - Extra: null, }, UtxoTransactionOutput: { - AddressId: 5, - IsUnspent: true, OutputIndex: 0, - TransactionId: 1, - UtxoTransactionOutputId: 1, ErgoBoxId: null, ErgoCreationHeight: null, ErgoRegisters: null, ErgoTree: null, - TokenListId: 1, }, tokens: [{ Token: { @@ -308,9 +303,6 @@ async function syncingSimpleTransaction( }, TokenList: { Amount: '2100000', - ListId: 1, - TokenId: 1, - TokenListItemId: 2, }, }], } @@ -386,32 +378,14 @@ async function syncingSimpleTransaction( }, output: { Transaction: { - Type: TransactionType.CardanoShelley, - ErrorMessage: null, Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', - Digest: 1.249559827714551e-31, - Ordinal: 0, - BlockId: 2, - LastUpdateTime: 1568392656000, - Status: 1, - TransactionId: 2, - Extra: { - Fee: '100000', - IsValid: true, - Metadata: null, - }, }, UtxoTransactionOutput: { - AddressId: 21, - IsUnspent: true, OutputIndex: 0, - TransactionId: 2, - UtxoTransactionOutputId: 3, ErgoBoxId: null, ErgoCreationHeight: null, ErgoRegisters: null, ErgoTree: null, - TokenListId: 5, }, tokens: [{ Token: { @@ -432,9 +406,6 @@ async function syncingSimpleTransaction( }, TokenList: { Amount: '1100000', - ListId: 5, - TokenId: 1, - TokenListItemId: 6, }, }], } @@ -451,32 +422,14 @@ async function syncingSimpleTransaction( }, output: { Transaction: { - Type: TransactionType.CardanoShelley, - ErrorMessage: null, Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', - Digest: 1.249559827714551e-31, - Ordinal: 0, - BlockId: 2, - LastUpdateTime: 1568392656000, - Status: 1, - TransactionId: 2, - Extra: { - Fee: '100000', - IsValid: true, - Metadata: null, - }, }, UtxoTransactionOutput: { - AddressId: 20, - IsUnspent: true, OutputIndex: 1, - TransactionId: 2, - UtxoTransactionOutputId: 4, ErgoBoxId: null, ErgoCreationHeight: null, ErgoRegisters: null, ErgoTree: null, - TokenListId: 6, }, tokens: [{ Token: { @@ -497,9 +450,6 @@ async function syncingSimpleTransaction( }, TokenList: { Amount: '900000', - ListId: 6, - TokenId: 1, - TokenListItemId: 7, }, }], }, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js index 609dcaf7f5..2907d60816 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js @@ -22,7 +22,8 @@ import { genGetBestBlock, getSingleAddressString, genGetTokenInfo, - genGetMultiAssetMetadata + genGetMultiAssetMetadata, + MockUtxoApi, } from '../../../state-fetch/mockNetwork'; import { HARD_DERIVATION_START, @@ -46,72 +47,91 @@ import { networks, } from '../../database/prepackaged/networks'; import { TransactionType } from '../../database/primitives/tables'; +import UtxoApi from '../../../state-fetch/utxoApi'; +import { RustModule } from '../../../cardanoCrypto/rustLoader'; jest.mock('../../database/initialSeed'); -const networkTransactions: number => Array = (purpose) => [{ - hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545', - height: 218608, - block_hash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba25', - time: '2019-09-13T16:37:16.000Z', - last_update: '2019-09-13T16:37:16.000Z', - tx_state: 'Successful', - tx_ordinal: 0, - epoch: 10, - slot: 3650, - inputs: [ - { - // 'Ae2tdPwUPEZ5PxKxoyZDgjsKgMWMpTRa4PH3sVgARSGBsWwNBH3qg7cMFsP' - address: getSingleAddressString( - ABANDON_SHARE, - [ - purpose, - CoinTypes.CARDANO, - 0 + HARD_DERIVATION_START, - ChainDerivations.EXTERNAL, - 7 - ] - ), - amount: '4000000', - id: '9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d200', - index: 0, - txHash: '9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d20', - assets: [], - } - ], - outputs: [ - { - // 'Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo' - address: getSingleAddressString( - TX_TEST_MNEMONIC_1, - [ - purpose, - CoinTypes.CARDANO, - 0 + HARD_DERIVATION_START, - ChainDerivations.EXTERNAL, - 4 - ] - ), - amount: '2100000', - assets: [], - }, - { - // 'Ae2tdPwUPEZE9RAm3d3zuuh22YjqDxhR1JF6G93uJsRrk51QGHzRUzLvDjL' - address: getSingleAddressString( - ABANDON_SHARE, - [ - purpose, - CoinTypes.CARDANO, - 0 + HARD_DERIVATION_START, - ChainDerivations.INTERNAL, - 12 - ] - ), - amount: '1731391', - assets: [], - } - ] -}]; +const networkTransactions: number => Array = (purpose) => [ + { + hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed544', + height: 218607, + block_hash: 'ba24', + time: '2019-09-13T16:37:16.000Z', + last_update: '2019-09-13T16:37:16.000Z', + tx_state: 'Successful', + tx_ordinal: 0, + epoch: 10, + slot: 3650, + inputs: [ + ], + outputs: [ + ] + }, + { + hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545', + height: 218608, + block_hash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba25', + time: '2019-09-13T16:37:16.000Z', + last_update: '2019-09-13T16:37:16.000Z', + tx_state: 'Successful', + tx_ordinal: 0, + epoch: 10, + slot: 3650, + inputs: [ + { + // 'Ae2tdPwUPEZ5PxKxoyZDgjsKgMWMpTRa4PH3sVgARSGBsWwNBH3qg7cMFsP' + address: getSingleAddressString( + ABANDON_SHARE, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.EXTERNAL, + 7 + ] + ), + amount: '4000000', + id: '9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d200', + index: 0, + txHash: '9c8d3c4fe576f8c99d8ad6ba5d889f5a9f2d7fe07dc17b3f425f5d17696f3d20', + assets: [], + } + ], + outputs: [ + { + // 'Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo' + address: getSingleAddressString( + TX_TEST_MNEMONIC_1, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.EXTERNAL, + 4 + ] + ), + amount: '2100000', + assets: [], + }, + { + // 'Ae2tdPwUPEZE9RAm3d3zuuh22YjqDxhR1JF6G93uJsRrk51QGHzRUzLvDjL' + address: getSingleAddressString( + ABANDON_SHARE, + [ + purpose, + CoinTypes.CARDANO, + 0 + HARD_DERIVATION_START, + ChainDerivations.INTERNAL, + 12 + ] + ), + amount: '1731391', + assets: [], + } + ] + } +]; const nextRegularSpend: number => RemoteTransaction = (purpose) => ({ hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', @@ -274,6 +294,10 @@ const twoTxsRegularSpend: number => Array = (purpose) => [{ ] }]; +beforeAll(async () => { + await RustModule.load(); +}); + beforeEach(() => { mockDate(); }); @@ -281,11 +305,13 @@ beforeEach(() => { async function syncingSimpleTransaction( purposeForTest: WalletTypePurposeT, ): Promise { + const txHistory = networkTransactions(purposeForTest); + UtxoApi.utxoApiFactory = (_: string) => new MockUtxoApi(txHistory, 1); + const db = await loadLovefieldDB(schema.DataStoreType.MEMORY); const publicDeriver = await setup(db, TX_TEST_MNEMONIC_1, purposeForTest); const network = networks.CardanoMainnet; - const txHistory = networkTransactions(purposeForTest); const checkAddressesInUse = genCheckAddressesInUse(txHistory, network); const getTransactionsHistoryForAddresses = genGetTransactionsHistoryForAddresses( txHistory, @@ -341,28 +367,14 @@ async function syncingSimpleTransaction( }, output: { Transaction: { - Type: TransactionType.CardanoByron, - ErrorMessage: null, Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed545', - Digest: 8.191593645542673e-27, - Ordinal: 0, - BlockId: 1, - LastUpdateTime: 1568392636000, - Status: 1, - TransactionId: 1, - Extra: null, }, UtxoTransactionOutput: { - AddressId: 5, - IsUnspent: true, OutputIndex: 0, - TransactionId: 1, - UtxoTransactionOutputId: 1, ErgoBoxId: null, ErgoCreationHeight: null, ErgoRegisters: null, ErgoTree: null, - TokenListId: 1, }, tokens: [{ Token: { @@ -383,9 +395,6 @@ async function syncingSimpleTransaction( }, TokenList: { Amount: '2100000', - ListId: 1, - TokenId: 1, - TokenListItemId: 2, }, }], } @@ -469,28 +478,14 @@ async function syncingSimpleTransaction( }, output: { Transaction: { - Type: TransactionType.CardanoByron, - ErrorMessage: null, Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', - Digest: 1.249559827714551e-31, - Ordinal: 0, - BlockId: 2, - LastUpdateTime: 1568392656000, - Status: 1, - TransactionId: 2, - Extra: null, }, UtxoTransactionOutput: { - AddressId: 21, - IsUnspent: true, OutputIndex: 0, - TransactionId: 2, - UtxoTransactionOutputId: 3, ErgoBoxId: null, ErgoCreationHeight: null, ErgoRegisters: null, ErgoTree: null, - TokenListId: 4, }, tokens: [{ Token: { @@ -511,9 +506,6 @@ async function syncingSimpleTransaction( }, TokenList: { Amount: '1100000', - ListId: 4, - TokenId: 1, - TokenListItemId: 5, }, }], } @@ -530,28 +522,14 @@ async function syncingSimpleTransaction( }, output: { Transaction: { - Type: TransactionType.CardanoByron, - ErrorMessage: null, Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', - Digest: 1.249559827714551e-31, - Ordinal: 0, - BlockId: 2, - LastUpdateTime: 1568392656000, - Status: 1, - TransactionId: 2, - Extra: null, }, UtxoTransactionOutput: { - AddressId: 20, - IsUnspent: true, OutputIndex: 1, - TransactionId: 2, - UtxoTransactionOutputId: 4, ErgoBoxId: null, ErgoCreationHeight: null, ErgoRegisters: null, ErgoTree: null, - TokenListId: 5, }, tokens: [{ Token: { @@ -572,9 +550,6 @@ async function syncingSimpleTransaction( }, TokenList: { Amount: '900000', - ListId: 5, - TokenId: 1, - TokenListItemId: 6, }, }], }, @@ -767,10 +742,12 @@ test('Syncing simple transaction bip44', async (done) => { async function utxoCreatedAndUsed( purposeForTest: WalletTypePurposeT, ): Promise { + const txHistory = networkTransactions(purposeForTest); + UtxoApi.utxoApiFactory = (_: string) => new MockUtxoApi(txHistory, 0); + const db = await loadLovefieldDB(schema.DataStoreType.MEMORY); const publicDeriver = await setup(db, TX_TEST_MNEMONIC_1, purposeForTest); - const txHistory = networkTransactions(purposeForTest); const network = networks.CardanoMainnet; const checkAddressesInUse = genCheckAddressesInUse(txHistory, network); const getTransactionsHistoryForAddresses = genGetTransactionsHistoryForAddresses( @@ -836,28 +813,14 @@ async function utxoCreatedAndUsed( }, output: { Transaction: { - Type: TransactionType.CardanoByron, - ErrorMessage: null, Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', - Digest: 1.249559827714551e-31, - Ordinal: 0, - BlockId: 2, - LastUpdateTime: 1568392656000, - Status: 1, - TransactionId: 2, - Extra: null, }, UtxoTransactionOutput: { - AddressId: 21, - IsUnspent: true, OutputIndex: 0, - TransactionId: 2, - UtxoTransactionOutputId: 3, ErgoBoxId: null, ErgoCreationHeight: null, ErgoRegisters: null, ErgoTree: null, - TokenListId: 4, }, tokens: [{ Token: { @@ -878,9 +841,6 @@ async function utxoCreatedAndUsed( }, TokenList: { Amount: '1100000', - ListId: 4, - TokenId: 1, - TokenListItemId: 5, }, }], } @@ -897,28 +857,14 @@ async function utxoCreatedAndUsed( }, output: { Transaction: { - Type: TransactionType.CardanoByron, - ErrorMessage: null, Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546', - Digest: 1.249559827714551e-31, - Ordinal: 0, - BlockId: 2, - LastUpdateTime: 1568392656000, - Status: 1, - TransactionId: 2, - Extra: null, }, UtxoTransactionOutput: { - AddressId: 20, - IsUnspent: true, OutputIndex: 1, - TransactionId: 2, - UtxoTransactionOutputId: 4, ErgoBoxId: null, ErgoCreationHeight: null, ErgoRegisters: null, ErgoTree: null, - TokenListId: 5, }, tokens: [{ Token: { @@ -939,9 +885,6 @@ async function utxoCreatedAndUsed( }, TokenList: { Amount: '900000', - ListId: 5, - TokenId: 1, - TokenListItemId: 6, }, }], } diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/status.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/status.test.js index 09ddc867bf..7388301f0e 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/status.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/status.test.js @@ -22,6 +22,7 @@ import { getSingleAddressString, genGetTokenInfo, genGetMultiAssetMetadata, + MockUtxoApi, } from '../../../state-fetch/mockNetwork'; import { loadLovefieldDB } from '../../database/index'; import { @@ -45,9 +46,25 @@ import { updateTransactions, getAllTransactions } from '../updateTransactions'; import { TransactionType } from '../../database/primitives/tables'; +import UtxoApi from '../../../state-fetch/utxoApi'; +import { RustModule } from '../../../cardanoCrypto/rustLoader'; jest.mock('../../database/initialSeed'); +const placeholderTx = { + hash: 'hash0', + height: 218607, + block_hash: 'a9835cc1e0f9b6c239aec4c446a6e181b7db6a80ad53cc0b04f70c6b85e9ba24', + time: '2019-09-13T16:37:16.000Z', + last_update: '2019-09-13T16:37:16.000Z', + tx_state: 'Successful', + tx_ordinal: 0, + epoch: 10, + slot: 3650, + inputs: [], + outputs: [], +}; + const initialPendingTx: ('Failed' | 'Pending', number) => RemoteTransaction = ( state, purpose @@ -280,6 +297,10 @@ const pointlessTx: number => RemoteTransaction = purpose => Object.freeze({ ] }); +beforeAll(async () => { + await RustModule.load(); +}); + beforeEach(() => { mockDate(); }); @@ -288,11 +309,16 @@ async function baseTest( type: 'Pending' | 'Failed', purposeForTest: WalletTypePurposeT, ): Promise { + const networkTransactions: Array = [ + placeholderTx, + initialPendingTx(type, purposeForTest) + ]; + UtxoApi.utxoApiFactory = (_: string) => new MockUtxoApi(networkTransactions, 0); + const db = await loadLovefieldDB(schema.DataStoreType.MEMORY); const publicDeriver = await setup(db, TX_TEST_MNEMONIC_1, purposeForTest); const network = networks.CardanoMainnet; - const networkTransactions: Array = [initialPendingTx(type, purposeForTest)]; const checkAddressesInUse = genCheckAddressesInUse(networkTransactions, network); const getTransactionsHistoryForAddresses = genGetTransactionsHistoryForAddresses( networkTransactions, @@ -323,7 +349,7 @@ async function baseTest( ); { - const response = await basePubDeriver.getAllUtxos(); + const response = await basePubDeriver.getAllUtxosFromOldDb(); expect(response).toEqual([]); } @@ -341,7 +367,7 @@ async function baseTest( const response = await basePubDeriver.getCutoff(); expect(response).toEqual(0); } - + /* { const response = await publicDeriver.getLastSyncInfo(); expect(response).toEqual({ @@ -352,6 +378,7 @@ async function baseTest( Time: new Date(0), }); } + */ } // adding regular tx while pending tx still exists @@ -376,7 +403,7 @@ async function baseTest( ChainDerivations.EXTERNAL, 4 ]; - const response = await basePubDeriver.getAllUtxos(); + const response = await basePubDeriver.getAllUtxosFromOldDb(); expect(response).toEqual([{ // 'Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo' address: getSingleAddressString( @@ -469,7 +496,7 @@ async function baseTest( // pending becomes successful { - const previouslyPending: RemoteTransaction = networkTransactions.shift(); + const previouslyPending: RemoteTransaction = networkTransactions.splice(1, 1)[0]; const newTx = { ...previouslyPending, ...({ @@ -503,7 +530,7 @@ async function baseTest( ChainDerivations.EXTERNAL, 4 ]; - const response = await basePubDeriver.getAllUtxos(); + const response = await basePubDeriver.getAllUtxosFromOldDb(); expect(response).toEqual([{ // 'Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo' address: getSingleAddressString( @@ -678,7 +705,7 @@ async function baseTest( ); { - const response = await basePubDeriver.getAllUtxos(); + const response = await basePubDeriver.getAllUtxosFromOldDb(); const expectedAddressing = [ purposeForTest, CoinTypes.CARDANO, @@ -886,14 +913,61 @@ async function baseTest( Extra: null, }]); + // Note currently networkTransactions = [ placerholderTx, otherSpend ], + // so actually this proves that the original UTXO set result is *wrong*. { - const response = await basePubDeriver.getAllUtxos(); + const response = await basePubDeriver.getAllUtxosFromOldDb(); expect(response).toEqual([]); } + { + const response = await basePubDeriver.getAllUtxos(); + expect(response).toEqual([ + { + output: { + Transaction: { + Hash: '29f2fe214ec2c9b05773a689eca797e903adeaaf51dfe20782a4bf401e7ed546' + }, + UtxoTransactionOutput: { + OutputIndex: 0, + ErgoBoxId: null, + ErgoCreationHeight: null, + ErgoTree: null, + ErgoRegisters: null + }, + tokens: [ + { + Token: { + Digest: 6.262633522161549e-167, + NetworkId: 0, + Identifier: '', + IsDefault: true, + IsNFT: false, + Metadata: { + type: 'Cardano', + policyId: '', + assetName: '', + ticker: 'ADA', + longName: null, + numberOfDecimals: 6 + }, + TokenId: 1 + }, + TokenList: { Amount: '2100000' } + } + ] + }, + addressing: { + path: [ 2147483692, 2147485463, 2147483648, 0, 4 ], + startLevel: 1 + }, + address: 'Ae2tdPwUPEZ6tzHKyuMLL6bh1au5DETgb53PTmJAN9aaCLtaUTWHvrS2mxo' + } + ]); + } { const response = await basePubDeriver.getUtxoBalance(); - expect(response.getDefault()).toEqual(new BigNumber('0')); + expect(response.getDefault()).toEqual(new BigNumber('2100000')); } } @@ -929,14 +1003,16 @@ test('Syncing with failed bip44', async (done) => { async function pendingDropped( purposeForTest: WalletTypePurposeT, ): Promise { - const db = await loadLovefieldDB(schema.DataStoreType.MEMORY); - const publicDeriver = await setup(db, TX_TEST_MNEMONIC_1, purposeForTest); - // need pointless tx otherwise the remote response is ignore since remote has empty blockchain const networkTransactions = [ pointlessTx(purposeForTest), initialPendingTx('Pending', purposeForTest) ]; + UtxoApi.utxoApiFactory = (_: string) => new MockUtxoApi(networkTransactions, 0); + + const db = await loadLovefieldDB(schema.DataStoreType.MEMORY); + const publicDeriver = await setup(db, TX_TEST_MNEMONIC_1, purposeForTest); + const network = networks.CardanoMainnet; const checkAddressesInUse = genCheckAddressesInUse(networkTransactions, network); const getTransactionsHistoryForAddresses = genGetTransactionsHistoryForAddresses( diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js index 5ddbe4b4ee..b74cafc0bb 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js @@ -151,7 +151,6 @@ import type { DefaultTokenEntry, } from '../../../../common/lib/MultiToken'; import { UtxoStorageApi } from '../models/utils'; -import { PublicDeriver } from '../models/PublicDeriver'; type TokensMintMetadata = {| ...{[key: string]: TokenMintMetadata[]} @@ -872,6 +871,10 @@ export async function removeAllTransactions( .keys(deps) .map(key => deps[key]) .flatMap(table => getAllSchemaTables(db, table)); + const updateUtxoTables = Object + .keys(UtxoStorageApi.depsTables) + .map(key => UtxoStorageApi.depsTables[key]) + .flatMap(table => getAllSchemaTables(db, table)); return await raii>( db, @@ -880,13 +883,24 @@ export async function removeAllTransactions( ...db.getSchema().tables(), ...depTables, ...mapToTables(db, derivationTables), + ...updateUtxoTables, ], - async dbTx => rawRemoveAllTransactions( - db, dbTx, - deps, - request.publicDeriver.getParent().getDerivationTables(), - { publicDeriver: request.publicDeriver }, - ) + async dbTx => { + await UtxoStorageApi.depsTables.ModifyUtxoAtSafePoint.remove( + db, dbTx, + request.publicDeriver.getPublicDeriverId(), + ); + await UtxoStorageApi.depsTables.ModifyUtxoDiffToBestBlock.removeAll( + db, dbTx, + request.publicDeriver.getPublicDeriverId(), + ); + return rawRemoveAllTransactions( + db, dbTx, + deps, + request.publicDeriver.getParent().getDerivationTables(), + { publicDeriver: request.publicDeriver }, + ); + } ); } @@ -904,6 +918,11 @@ export async function updateTransactions( ? new Map() : withLevels.getParent().getDerivationTables(); let lastSyncInfo = undefined; + const updateUtxoTables = Object + .keys(UtxoStorageApi.depsTables) + .map(key => UtxoStorageApi.depsTables[key]) + .flatMap(table => getAllSchemaTables(db, table)); + try { const updateDepTables = Object.freeze({ GetLastSyncForPublicDeriver, @@ -941,11 +960,6 @@ export async function updateTransactions( .map(key => updateDepTables[key]) .flatMap(table => getAllSchemaTables(db, table)); - const updateUtxoTables = Object - .keys(UtxoStorageApi.depsTables) - .map(key => UtxoStorageApi.depsTables[key]) - .flatMap(table => getAllSchemaTables(db, table)); - await raii( db, [ @@ -964,7 +978,8 @@ export async function updateTransactions( ...remainingDeps } = updateDepTables; - const addresses = await rawUpdateTransactions( + + await rawUpdateTransactions( db, dbTx, remainingDeps, publicDeriver, @@ -977,16 +992,12 @@ export async function updateTransactions( getMultiAssetMetadata ); - if (addresses) { - if (!(publicDeriver instanceof PublicDeriver)) { - throw new Error('unxpected publicDeriver type'); - } - await updateUtxos( - db, dbTx, - publicDeriver, - addresses, - ); - } + await updateUtxos( + db, dbTx, + publicDeriver, + remainingDeps, + derivationTables, + ); } ); } catch (e) { @@ -1023,6 +1034,7 @@ export async function updateTransactions( [ ...rollbackTables, ...mapToTables(db, derivationTables), + ...updateUtxoTables, ], async dbTx => { const newLastSyncInfo = await rollbackDepTables.GetLastSyncForPublicDeriver.forId( @@ -1045,6 +1057,13 @@ export async function updateTransactions( }, derivationTables ); + + await updateUtxos( + db, dbTx, + publicDeriver, + rollbackDepTables, + derivationTables, + ); } ); } @@ -1205,8 +1224,6 @@ async function rollback( // note: we don't modify the display cutoff since it may confuse the user to suddenly shrink it } -// return all addresses that may have transactions, or undefined if there is no need -// for updating async function rawUpdateTransactions( db: lf$Database, dbTx: lf$Transaction, @@ -1248,7 +1265,7 @@ async function rawUpdateTransactions( derivationTables: Map, getTokenInfo: TokenInfoFunc, getMultiAssetMetadata: MultiAssetMintMetadataFunc, -): Promise | void> { +): Promise { const network = publicDeriver.getParent().getNetworkInfo(); // TODO: consider passing this function in as an argument instead of generating it here const toAbsoluteSlotNumber = await genToAbsoluteSlotNumber( @@ -1268,8 +1285,6 @@ async function rawUpdateTransactions( } } - let requestAddresses = undefined; - if (bestBlock.hash != null) { const untilBlock = bestBlock.hash; @@ -1329,35 +1344,10 @@ async function rawUpdateTransactions( tx: bestInStorage.Transaction.Hash, } }; - requestAddresses = [ - // needs to send legacy addresses directly since they don't use the payment key method - ...addresses.utxoAddresses - .filter(address => address.Type === CoreAddressTypes.CARDANO_LEGACY) - .map(address => address.Hash), - // payment keys will fetch all addresses with the same payment key - ...addresses.utxoAddresses - .filter(address => address.Type === CoreAddressTypes.CARDANO_ENTERPRISE) - .reduce( - (list, next) => { - const wasmAddr = RustModule.WalletV4.Address.from_bytes(Buffer.from(next.Hash, 'hex')); - const enterpriseWasm = RustModule.WalletV4.EnterpriseAddress.from_address(wasmAddr); - if (enterpriseWasm == null) return list; - const keyHash = enterpriseWasm.payment_cred().to_keyhash(); - if (keyHash == null) return list; - list.push(keyHash.to_bech32(Bech32Prefix.PAYMENT_KEY_HASH)); - return list; - }, - [] - ), - // note: sending account addresses is required - // since for example, the staking key registration certificate doesn't need a witness - // so a tx where no input/output belongs to you could register your staking key - ...addresses.accountingAddresses.map(address => address.Hash), - ]; const txsFromNetwork = await getTransactionsHistoryForAddresses({ ...requestKind, network, - addresses: requestAddresses, + addresses: toRequestAddresses(addresses), untilBlock, }); @@ -1426,8 +1416,6 @@ async function rawUpdateTransactions( Height: bestBlock.height, } ); - - return requestAddresses; } /** @@ -2865,13 +2853,66 @@ async function certificateToDb( async function updateUtxos( db: lf$Database, dbTx: lf$Transaction, - publicDeriver: PublicDeriver<>, - addresses: Array, + publicDeriver: IPublicDeriver<>, + deps: { + GetPathWithSpecific: Class, + GetAddress: Class, + GetDerivationSpecific: Class, + ... + }, + derivationTables: Map, ) { - const { utxoStorageApi, utxoService, } = publicDeriver; + // Note: we are still using the pre-Yoroi-lib-UtxoService data to get the set of + // wallet addresses. May change in the future. + const addresses = await rawGetAddressRowsForWallet( + dbTx, + { + GetPathWithSpecific: deps.GetPathWithSpecific, + GetAddress: deps.GetAddress, + GetDerivationSpecific: deps.GetDerivationSpecific, + }, + { publicDeriver }, + derivationTables, + ); + + const utxoStorageApi = publicDeriver.getUtxoStorageApi(); + const utxoService = publicDeriver.getUtxoService(); utxoStorageApi.setDb(db); utxoStorageApi.setDbTx(dbTx); - await utxoService.syncUtxoState(addresses); + await utxoService.syncUtxoState(toRequestAddresses(addresses)); +} + +function toRequestAddresses( + addresses: {| + utxoAddresses: Array<$ReadOnly>, + accountingAddresses: Array<$ReadOnly>, + |} +): Array { + return [ + // needs to send legacy addresses directly since they don't use the payment key method + ...addresses.utxoAddresses + .filter(address => address.Type === CoreAddressTypes.CARDANO_LEGACY) + .map(address => address.Hash), + // payment keys will fetch all addresses with the same payment key + ...addresses.utxoAddresses + .filter(address => address.Type === CoreAddressTypes.CARDANO_ENTERPRISE) + .reduce( + (list, next) => { + const wasmAddr = RustModule.WalletV4.Address.from_bytes(Buffer.from(next.Hash, 'hex')); + const enterpriseWasm = RustModule.WalletV4.EnterpriseAddress.from_address(wasmAddr); + if (enterpriseWasm == null) return list; + const keyHash = enterpriseWasm.payment_cred().to_keyhash(); + if (keyHash == null) return list; + list.push(keyHash.to_bech32(Bech32Prefix.PAYMENT_KEY_HASH)); + return list; + }, + [] + ), + // note: sending account addresses is required + // since for example, the staking key registration certificate doesn't need a witness + // so a tx where no input/output belongs to you could register your staking key + ...addresses.accountingAddresses.map(address => address.Hash), + ]; } diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/tests/__snapshots__/index.test.js.snap b/packages/yoroi-extension/app/api/ada/lib/storage/tests/__snapshots__/index.test.js.snap index 997569bce0..f8e21c9149 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/tests/__snapshots__/index.test.js.snap +++ b/packages/yoroi-extension/app/api/ada/lib/storage/tests/__snapshots__/index.test.js.snap @@ -1467,6 +1467,8 @@ Object { "TokenList": Array [], "Transaction": Array [], "TxMemo": Array [], + "UtxoAtSafePointTable": Array [], + "UtxoDiffToBestBlock": Array [], "UtxoTransactionInput": Array [], "UtxoTransactionOutput": Array [], } diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index 58d8ae2277..99cb980b2a 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.13.100", + "version": "4.14.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1722,9 +1722,9 @@ } }, "@emurgo/yoroi-lib-core": { - "version": "0.4.1-alpha.28", - "resolved": "https://registry.npmjs.org/@emurgo/yoroi-lib-core/-/yoroi-lib-core-0.4.1-alpha.28.tgz", - "integrity": "sha512-jBz36hGdTrS36nskV00W6GiVNxpq8nojgqS37ySI2U12uYZ0v9imWDl64nyt5IBrG9uP+sK13RM+lrRZsLNFRQ==", + "version": "0.4.3-alpha.30", + "resolved": "https://registry.npmjs.org/@emurgo/yoroi-lib-core/-/yoroi-lib-core-0.4.3-alpha.30.tgz", + "integrity": "sha512-3fvesXfBDrOj8w4TOSryYKOkzH2dtKKQaCi/ZQwAelkpVBogWdrIWEnVf2KWZ8ac1TA4nG4vuUmYY3xeWD0SOw==", "requires": { "axios": "^0.24.0", "bignumber.js": "^9.0.1", @@ -7457,6 +7457,12 @@ "defer-to-connect": "^2.0.0" } }, + "@testim/chrome-version": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.2.tgz", + "integrity": "sha512-1c4ZOETSRpI0iBfIFUqU4KqwBAB2lHUAlBjZz/YqOHqwM9dTTzjV6Km0ZkiEiSCx/tLr1BtESIKyWWMww+RUqw==", + "dev": true + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -8008,6 +8014,16 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, + "@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -10600,6 +10616,32 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, + "chromedriver": { + "version": "101.0.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-101.0.0.tgz", + "integrity": "sha512-LkkWxy6KM/0YdJS8qBeg5vfkTZTRamhBfOttb4oic4echDgWvCU1E8QcBbUBOHqZpSrYMyi7WMKmKMhXFUaZ+w==", + "dev": true, + "requires": { + "@testim/chrome-version": "^1.1.2", + "axios": "^0.24.0", + "del": "^6.0.0", + "extract-zip": "^2.0.1", + "https-proxy-agent": "^5.0.0", + "proxy-from-env": "^1.1.0", + "tcp-port-used": "^1.0.1" + }, + "dependencies": { + "axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "dev": true, + "requires": { + "follow-redirects": "^1.14.4" + } + } + } + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -12455,6 +12497,22 @@ } } }, + "del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dev": true, + "requires": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -14343,6 +14401,29 @@ } } }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -16674,6 +16755,12 @@ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, + "ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "dev": true + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -16974,6 +17061,12 @@ "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", "dev": true }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -17079,6 +17172,12 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true + }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -17117,6 +17216,17 @@ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, + "is2": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.7.tgz", + "integrity": "sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" + } + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -25915,6 +26025,27 @@ "readable-stream": "^3.1.1" } }, + "tcp-port-used": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", + "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", + "dev": true, + "requires": { + "debug": "4.3.1", + "is2": "^2.0.6" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, "telejson": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/telejson/-/telejson-5.3.3.tgz", diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index 873965ec19..3282035c3e 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -176,7 +176,7 @@ "@emurgo/cip4-js": "1.0.5", "@emurgo/js-chain-libs": "0.7.1", "@emurgo/ledger-connect-handler": "7.0.1", - "@emurgo/yoroi-lib-core": "^0.4.1-alpha.28", + "@emurgo/yoroi-lib-core": "0.4.3-alpha.30", "@mui/lab": "^5.0.0-alpha.51", "@mui/material": "^5.0.4", "@svgr/webpack": "5.5.0", From 59c0a98e0086838eb34ad82233acc8c3f4d6828f Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 30 May 2022 11:50:40 +0800 Subject: [PATCH 013/199] lint --- .../api/ada/lib/state-fetch/mockNetwork.js | 17 +++++++------ .../app/api/ada/lib/storage/adaMigration.js | 1 - .../storage/bridge/tests/multiwallet.test.js | 1 - .../lib/storage/bridge/tests/shelley.test.js | 1 - .../storage/bridge/tests/simpleTxs.test.js | 1 - .../ada/lib/storage/bridge/tests/utxo.test.js | 2 -- .../lib/storage/bridge/updateTransactions.js | 2 +- .../ada/lib/storage/database/utxo/api/read.js | 5 ++-- .../lib/storage/database/utxo/api/write.js | 2 +- .../ada/lib/storage/database/utxo/tables.js | 3 +-- .../lib/storage/database/utxo/utxo.test.js | 4 ++-- .../models/PublicDeriver/interfaces.js | 10 ++------ .../storage/models/PublicDeriver/traits.js | 24 +++++++++---------- 13 files changed, 29 insertions(+), 44 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js index 428bfe2015..a341bfabbb 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js @@ -39,15 +39,12 @@ import { Bech32Prefix } from '../../../../config/stringConfig'; import { parseTokenList } from '../../transactions/utils'; import type { UtxoApiContract } from '@emurgo/yoroi-lib-core/dist/utxo/api'; import type { - Asset, - DiffPoint, TipStatusReference, Utxo, UtxoApiResponse, UtxoAtPointRequest, UtxoDiff, UtxoDiffItem, - UtxoDiffItemOutput, UtxoDiffSincePointRequest } from '@emurgo/yoroi-lib-core/dist/utxo/models'; import { UtxoApiResult, } from '@emurgo/yoroi-lib-core/dist/utxo/models'; @@ -705,7 +702,7 @@ export class MockUtxoApi implements UtxoApiContract { this.blockchain = blockchain; this.safeConfirmations = safeConfirmations; } - + _getLastSafeBlockTxIndex(): number { let lastHeight = null; let i; @@ -810,9 +807,11 @@ export class MockUtxoApi implements UtxoApiContract { ) ); // add new - tx.outputs.forEach((output, outputIndex) => { + for (let outputIndex = 0; outputIndex < tx.outputs.length; outputIndex++) { + const output = tx.outputs[outputIndex]; + if (!addresses.includes(output.address)) { - return; + continue; } const { height } = tx; @@ -833,7 +832,7 @@ export class MockUtxoApi implements UtxoApiContract { })), blockNum: height, }); - }); + }; } return { result: UtxoApiResult.SUCCESS, @@ -845,7 +844,7 @@ export class MockUtxoApi implements UtxoApiContract { const { addresses, untilBlockHash, afterBestBlock, } = req; let seenUntilBlock = false; - let utxoDiffItems = []; + const utxoDiffItems = []; for (let i = this.blockchain.length - 1; i >= 0; i--) { const tx = this.blockchain[i]; if (tx.tx_state !== 'Successful') { @@ -871,7 +870,7 @@ export class MockUtxoApi implements UtxoApiContract { type: 'output', id: utxoId, amount: new BigNumber(output.amount), - utxo:{ + utxo: { utxoId, txHash: tx.hash, txIndex: outputIndex, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js b/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js index 5be81fcb7e..1ff68cc541 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js @@ -383,7 +383,6 @@ export async function populateNewUtxodata( for (const publicDeriver of wallets) { try { - const withLevels = asHasLevels(publicDeriver); if (isErgo(publicDeriver.getParent().getNetworkInfo())) { continue; } diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js index 739360fa5e..48358dd565 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js @@ -49,7 +49,6 @@ import { import { networks, } from '../../database/prepackaged/networks'; -import { TransactionType } from '../../database/primitives/tables'; import UtxoApi from '../../../state-fetch/utxoApi'; import { RustModule } from '../../../cardanoCrypto/rustLoader'; diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js index edc94a93e1..d01d190d02 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js @@ -45,7 +45,6 @@ import { import { networks, } from '../../database/prepackaged/networks'; -import { TransactionType } from '../../database/primitives/tables'; import UtxoApi from '../../../state-fetch/utxoApi'; import { RustModule } from '../../../cardanoCrypto/rustLoader'; diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js index 2907d60816..2ed893ae92 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js @@ -46,7 +46,6 @@ import { import { networks, } from '../../database/prepackaged/networks'; -import { TransactionType } from '../../database/primitives/tables'; import UtxoApi from '../../../state-fetch/utxoApi'; import { RustModule } from '../../../cardanoCrypto/rustLoader'; diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js index 688b602ecc..48eb3f76fa 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js @@ -13,7 +13,6 @@ import { ABANDON_SHARE, TX_TEST_MNEMONIC_1, mockDate, - filterDbSnapshot, } from '../../../../../jestUtils'; import { genCheckAddressesInUse, @@ -45,7 +44,6 @@ import { import { networks, } from '../../database/prepackaged/networks'; -import { TransactionType } from '../../database/primitives/tables'; import UtxoApi from '../../../state-fetch/utxoApi'; import { RustModule } from '../../../cardanoCrypto/rustLoader'; diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js index b74cafc0bb..e934dc7feb 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js @@ -2861,7 +2861,7 @@ async function updateUtxos( ... }, derivationTables: Map, -) { +): Promise { // Note: we are still using the pre-Yoroi-lib-UtxoService data to get the set of // wallet addresses. May change in the future. const addresses = await rawGetAddressRowsForWallet( diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js index a7bef9b8aa..0ddbf0b78f 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/read.js @@ -10,7 +10,6 @@ import { } from '../../utils'; import * as Tables from '../tables'; import type { - UtxoAtSafePoint, UtxoAtSafePointRow, UtxoDiffToBestBlock, UtxoDiffToBestBlockRow, @@ -56,7 +55,9 @@ export class GetUtxoDiffToBestBlock { const rows = await getRowIn( db, tx, GetUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name].name, - GetUtxoDiffToBestBlock.ownTables[Tables.UtxoDiffToBestBlockSchema.name].properties.PublicDeriverId, + GetUtxoDiffToBestBlock.ownTables[ + Tables.UtxoDiffToBestBlockSchema.name + ].properties.PublicDeriverId, ([publicDeriverId]: Array), ); return rows.map(r => ({ diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js index b4e2917ce2..c33e231a72 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/api/write.js @@ -123,7 +123,7 @@ export class ModifyUtxoDiffToBestBlock { publicDeriverId: number, utxoDiffToBestBlock: UtxoDiffToBestBlock, ): Promise { - // Do nothing if a row with `utxoDiffToBestBlock.lastBestBlockHash` is already + // Do nothing if a row with `utxoDiffToBestBlock.lastBestBlockHash` is already // present. But we can't rely on the unique index because the exception is // thrown when the tx is being committed and there is no way to catch it // only for this query. diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js index 8e8f418bf1..5fe8a6f8f8 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/tables.js @@ -1,6 +1,5 @@ // @flow -import BigNumber from 'bignumber.js'; -import { Type, ConstraintAction, } from 'lovefield'; +import { Type } from 'lovefield'; import type { lf$schema$Builder } from 'lovefield'; import { PublicDeriverSchema } from '../walletTypes/core/tables'; diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js index 38c88947af..10e06d9f92 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/utxo/utxo.test.js @@ -132,7 +132,7 @@ test('UtxoAtSafePoint', async () => { publicDeriverId, ) ); - + expect(result).toEqual( expect.objectContaining({ UtxoAtSafePoint: UTXO_AT_SAFE_BLOCK_2 }) ); @@ -160,7 +160,7 @@ test('UtxoAtSafePoint', async () => { publicDeriverId, ) ); - + expect(result).toBe(undefined); }); diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js index 3245950e46..38be008f45 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js @@ -10,24 +10,18 @@ import type { IConceptualWallet } from '../ConceptualWallet/interfaces'; import { GetUtxoTxOutputsWithTx, } from '../../database/transactionModels/utxo/api/read'; -import type { UtxoTxOutput } from '../../database/transactionModels/utxo/api/read'; import type { - UtxoTransactionOutputRow, ErgoFields, } from '../../database/transactionModels/utxo/tables'; import type { - TransactionRow, TokenRow, - TokenListRow, -} from '../../database/primitives/tables'; - -import type { AddressRow, KeyRow, CanonicalAddressInsert, CanonicalAddressRow, KeyDerivationRow, } from '../../database/primitives/tables'; + import type { PublicDeriverRow, LastSyncInfoRow, } from '../../database/walletTypes/core/tables'; import type { @@ -46,6 +40,7 @@ import { GetKeyDerivation, GetKey, GetAddress, + GetToken, } from '../../database/primitives/api/read'; import type { CoreAddressT, @@ -73,7 +68,6 @@ import type { GetUtxoAtSafePoint, GetUtxoDiffToBestBlock, } from '../../database/utxo/api/read'; -import { GetToken } from '../../database/primitives/api/read'; import { UtxoService } from '@emurgo/yoroi-lib-core/dist/utxo'; import { UtxoStorageApi, } from '../utils'; diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js index d4e8cc201f..45cdab4da3 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js @@ -140,11 +140,9 @@ import { GetUtxoAtSafePoint, GetUtxoDiffToBestBlock, } from '../../database/utxo/api/read'; -import { PublicDeriver } from './'; import type { Utxo, } from '@emurgo/yoroi-lib-core/dist/utxo/models'; -import { isErgo } from '../../database/prepackaged/networks'; interface Empty {} type HasPrivateDeriverDependencies = IPublicDeriver; @@ -231,7 +229,7 @@ const GetAllUtxosMixin = ( ) => { // TODO: perhaps should use seperate types for Ergo and Cardano wallets instead // of branching - if (!isErgo(this.getParent().getNetworkInfo())) { + if (isCardanoHaskell(this.getParent().getNetworkInfo())) { const addresses = await this.rawGetAllUtxoAddresses( tx, { @@ -249,17 +247,17 @@ const GetAllUtxosMixin = ( const utxosInStorage: Array = await this.getUtxoService().getAvailableUtxos(); const networkId = this.getParent().getNetworkInfo().NetworkId; - const tokens = (await deps.GetToken.fromIdentifier( - super.getDb(), tx, - [ - '', - ...utxosInStorage.flatMap( - ({ assets }) => assets.map(asset => asset.assetId) - ) - ] - )).filter(token => token.NetworkId === networkId); const tokenMap = new Map>( - tokens.map(token => [ token.Identifier, token ]) + (await deps.GetToken.fromIdentifier( + super.getDb(), tx, + [ + '', + ...utxosInStorage.flatMap( + ({ assets }) => assets.map(asset => asset.assetId) + ) + ] + )).filter(token => token.NetworkId === networkId) + .map(token => [ token.Identifier, token ]) ); const addressingMap = new Map( From 9f0cf47bfd2081d2b60ae6f7c20c305f7d92ee9c Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 13 Jun 2022 18:38:56 +0300 Subject: [PATCH 014/199] package-lock update --- packages/yoroi-extension/package-lock.json | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index 96295a5e1a..013dcede12 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -1721,6 +1721,26 @@ "@cardano-foundation/ledgerjs-hw-app-cardano": "5.0.0" } }, + "@emurgo/yoroi-lib-core": { + "version": "0.4.3-alpha.30", + "resolved": "https://registry.npmjs.org/@emurgo/yoroi-lib-core/-/yoroi-lib-core-0.4.3-alpha.30.tgz", + "integrity": "sha512-3fvesXfBDrOj8w4TOSryYKOkzH2dtKKQaCi/ZQwAelkpVBogWdrIWEnVf2KWZ8ac1TA4nG4vuUmYY3xeWD0SOw==", + "requires": { + "axios": "^0.24.0", + "bignumber.js": "^9.0.1", + "hash-wasm": "^4.9.0" + }, + "dependencies": { + "axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "requires": { + "follow-redirects": "^1.14.4" + } + } + } + }, "@eslint/eslintrc": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", @@ -16175,6 +16195,11 @@ "safe-buffer": "^5.2.0" } }, + "hash-wasm": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.9.0.tgz", + "integrity": "sha512-7SW7ejyfnRxuOc7ptQHSf4LDoZaWOivfzqw+5rpcQku0nHfmicPKE51ra9BiRLAmT8+gGLestr1XroUkqdjL6w==" + }, "hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", From ce875c03f25ab89416f5f9707af8e6740e22c5f2 Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 27 Jun 2022 23:07:42 +0800 Subject: [PATCH 015/199] bump db schema version --- .../yoroi-extension/app/api/ada/lib/storage/database/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js index 32dfc9bd1b..2828e1da1c 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/index.js @@ -203,7 +203,7 @@ export async function copyDbToMemory( const populateAndCreate = async ( storeType: $Values ): Promise => { - const schemaVersion = 16; + const schemaVersion = 17; const schemaBuilder = schema.create(schemaName, schemaVersion); populatePrimitivesDb(schemaBuilder); From 08a67c9a8c6cd3676c958409a692dd9cf3b2b4c2 Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 28 Jun 2022 00:27:09 +0800 Subject: [PATCH 016/199] bug fix --- packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js | 2 +- .../app/api/ada/lib/storage/models/PublicDeriver/traits.js | 2 +- .../yoroi-extension/app/stores/toplevel/TransactionsStore.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js index cb4c6d3faf..19e5355478 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js @@ -9,7 +9,7 @@ import type { UtxoApiContract } from '@emurgo/yoroi-lib-core/dist/utxo/api'; export default class UtxoApi extends BatchedEmurgoUtxoApi { // so that the unit tests can override it with mocks static utxoApiFactory: (string) => UtxoApiContract = (backendServiceUrl) => - new EmurgoUtxoApi(axios, backendServiceUrl + '/', true); + new EmurgoUtxoApi(axios, backendServiceUrl + '/api/', true); constructor(backendServiceUrl: string) { const utxoApi = UtxoApi.utxoApiFactory(backendServiceUrl); diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js index 45cdab4da3..7f049aa654 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js @@ -278,7 +278,7 @@ const GetAllUtxosMixin = ( if (i === 0) { amount = utxo.amount; } else { - amount = utxo.assets[i].amount; + amount = utxo.assets[i-1].amount; } const token = tokenMap.get(tokenId); if (!token) { diff --git a/packages/yoroi-extension/app/stores/toplevel/TransactionsStore.js b/packages/yoroi-extension/app/stores/toplevel/TransactionsStore.js index acf6adaafe..2f22ed470c 100644 --- a/packages/yoroi-extension/app/stores/toplevel/TransactionsStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/TransactionsStore.js @@ -595,7 +595,7 @@ export default class TransactionsStore extends Store { { ...batchRequest, // only the first call should update from remote - isLocalRequest: i > 0, + isLocalRequest: request.isLocalRequest || i > 0, } ); if (batchResult.transactions.length === 0) { From f193ef2e21d291ba9c24fb6df6958ffb55b68cf8 Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 28 Jun 2022 01:11:13 +0800 Subject: [PATCH 017/199] bump yoroi-lib version --- packages/yoroi-extension/package-lock.json | 48 +++++++++++++++------- packages/yoroi-extension/package.json | 2 +- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index 013dcede12..9f14de06a4 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -1722,11 +1722,13 @@ } }, "@emurgo/yoroi-lib-core": { - "version": "0.4.3-alpha.30", - "resolved": "https://registry.npmjs.org/@emurgo/yoroi-lib-core/-/yoroi-lib-core-0.4.3-alpha.30.tgz", - "integrity": "sha512-3fvesXfBDrOj8w4TOSryYKOkzH2dtKKQaCi/ZQwAelkpVBogWdrIWEnVf2KWZ8ac1TA4nG4vuUmYY3xeWD0SOw==", + "version": "0.7.3-alpha.38", + "resolved": "https://registry.npmjs.org/@emurgo/yoroi-lib-core/-/yoroi-lib-core-0.7.3-alpha.38.tgz", + "integrity": "sha512-HVqOm8N9OTZlgVuKwtM1lX04ZNQqEz1z+6vqXs1Patf1zyUSwa1oVYUnAe6UI9PD10uh2Xljb49HQUqZcBv/xQ==", "requires": { + "@cardano-foundation/ledgerjs-hw-app-cardano": "^5.0.0", "axios": "^0.24.0", + "bech32": "^2.0.0", "bignumber.js": "^9.0.1", "hash-wasm": "^4.9.0" }, @@ -10617,13 +10619,13 @@ "dev": true }, "chromedriver": { - "version": "100.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-100.0.0.tgz", - "integrity": "sha512-oLfB0IgFEGY9qYpFQO/BNSXbPw7bgfJUN5VX8Okps9W2qNT4IqKh5hDwKWtpUIQNI6K3ToWe2/J5NdpurTY02g==", + "version": "103.0.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-103.0.0.tgz", + "integrity": "sha512-7BHf6HWt0PeOHCzWO8qlnD13sARzr5AKTtG8Csn+czsuAsajwPxdLNtry5GPh8HYFyl+i0M+yg3bT43AGfgU9w==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.2", - "axios": "^0.24.0", + "axios": "^0.27.2", "del": "^6.0.0", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.0", @@ -10632,12 +10634,30 @@ }, "dependencies": { "axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "dev": true, "requires": { - "follow-redirects": "^1.14.4" + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "dev": true + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" } } } @@ -12498,9 +12518,9 @@ } }, "del": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", - "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", "dev": true, "requires": { "globby": "^11.0.1", diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index 358918aad1..b925a36f35 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -176,7 +176,7 @@ "@emurgo/cip4-js": "1.0.5", "@emurgo/js-chain-libs": "0.7.1", "@emurgo/ledger-connect-handler": "7.0.1", - "@emurgo/yoroi-lib-core": "0.4.3-alpha.30", + "@emurgo/yoroi-lib-core": "0.7.3-alpha.38", "@mui/lab": "^5.0.0-alpha.51", "@mui/material": "^5.0.4", "@svgr/webpack": "5.5.0", From d52588297f65afaa1264906cb8c359dfd155e083 Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 28 Jun 2022 12:05:47 +0800 Subject: [PATCH 018/199] bug fix --- .../api/ada/lib/storage/models/PublicDeriver/traits.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js index 7f049aa654..95ad1473bb 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js @@ -268,7 +268,15 @@ const GetAllUtxosMixin = ( ); const addressedUtxos = utxosInStorage.map(utxo => { - const addressingInfo = addressingMap.get(utxo.receiver); + let addressHash; + try { + addressHash = Buffer.from( + RustModule.WalletV4.Address.from_bech32(utxo.receiver).to_bytes() + ).toString('hex') + } catch { + addressHash = utxo.receiver; + } + const addressingInfo = addressingMap.get(addressHash); if (addressingInfo == null) { throw new Error(`${nameof(GetAllUtxos)}::${nameof(this.rawGetAllUtxos)} should never happen`); } From 5fb8f33bdd30e10c6a13af857b6d90911a9fed8a Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 28 Jun 2022 16:30:33 +0800 Subject: [PATCH 019/199] bump yoroi-lib version --- packages/yoroi-extension/package-lock.json | 6 +++--- packages/yoroi-extension/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index 9f14de06a4..f62ea88cf0 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -1722,9 +1722,9 @@ } }, "@emurgo/yoroi-lib-core": { - "version": "0.7.3-alpha.38", - "resolved": "https://registry.npmjs.org/@emurgo/yoroi-lib-core/-/yoroi-lib-core-0.7.3-alpha.38.tgz", - "integrity": "sha512-HVqOm8N9OTZlgVuKwtM1lX04ZNQqEz1z+6vqXs1Patf1zyUSwa1oVYUnAe6UI9PD10uh2Xljb49HQUqZcBv/xQ==", + "version": "0.7.4-alpha.39", + "resolved": "https://registry.npmjs.org/@emurgo/yoroi-lib-core/-/yoroi-lib-core-0.7.4-alpha.39.tgz", + "integrity": "sha512-bxOM1nO9NcBoUfyMk74MngDcSVbv85cNjuMiKAN7bME69bAPSJLcYi51MCtYWHMNeP2I8TvAZmzB6Z68XMZg7g==", "requires": { "@cardano-foundation/ledgerjs-hw-app-cardano": "^5.0.0", "axios": "^0.24.0", diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index b925a36f35..2d66afceae 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -176,7 +176,7 @@ "@emurgo/cip4-js": "1.0.5", "@emurgo/js-chain-libs": "0.7.1", "@emurgo/ledger-connect-handler": "7.0.1", - "@emurgo/yoroi-lib-core": "0.7.3-alpha.38", + "@emurgo/yoroi-lib-core": "0.7.4-alpha.39", "@mui/lab": "^5.0.0-alpha.51", "@mui/material": "^5.0.4", "@svgr/webpack": "5.5.0", From 0fd1a7a0da03ceb67971133b6570f16eddd1a2ff Mon Sep 17 00:00:00 2001 From: yushi Date: Wed, 29 Jun 2022 12:26:20 +0800 Subject: [PATCH 020/199] add new API endpoints to mock server --- .../mock-chain/mockCardanoImporter.js | 3 + .../features/mock-chain/mockCardanoServer.js | 87 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoImporter.js b/packages/yoroi-extension/features/mock-chain/mockCardanoImporter.js index ae2fff71d8..6b655db830 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoImporter.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoImporter.js @@ -34,6 +34,7 @@ import { getAddressForType, getMangledAddressString, toRemoteByronTx, + MockUtxoApi, } from '../../app/api/ada/lib/state-fetch/mockNetwork'; import { networks, @@ -2037,6 +2038,7 @@ export const generateTransaction = (): {| // ================= const transactions: Array = []; +const mockUtxoApi: MockUtxoApi = new MockUtxoApi(transactions, 1); export function addTransaction(tx: MockTx): void { // need to insert txs in order they appear in the blockchain @@ -2329,4 +2331,5 @@ export default { getRewardHistory, getAccountState, getUtxoData, + mockUtxoApi, }; diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js index 988aaedece..cbe97f028b 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js @@ -213,6 +213,93 @@ export function getMockServer( res.send('Transaction not found'); }); + server.get('/api/v2/tipStatus', async (req, res) => { + const bestBlockHash = await mockImporter.mockUtxoApi.getBestBlock(); + const safeBlockHash = await mockImporter.mockUtxoApi.getSafeBlock(); + res.send({ + safeBlocK: { hash: safeBlockHash }, + bestBlock: { hash: bestBlockHash }, + }); + }); + + server.post('/api/v2/tipStatus', async (req, res) => { + const { bestBlocks } = req.body.reference; + const tipStatus = await mockImporter.mockUtxoApi.getTipStatusWithReference( + bestBlocks + ); + if (tipStatus.result !== 'SUCCESS') { + res.status(500); + res.send({ error: { response: 'REFERENCE_POINT_BLOCK_NOT_FOUND' } }); + } else { + const value = tipStatus.value; + if (!value) { + throw new Error('unpected null value'); + } + const bestBlockHash = await mockImporter.mockUtxoApi.getBestBlock(); + const safeBlockHash = await mockImporter.mockUtxoApi.getSafeBlock(); + res.send({ + safeBlock: safeBlockHash, + bestBlock: bestBlockHash, + reference: value.reference, + }); + } + }); + + server.post('/api/v2/txs/utxoAtPoint', async (req, res) => { + const { addresses, referenceBlockHash } = req.body; + const { value } = await mockImporter.mockUtxoApi.getUtxoAtPoint( + { addresses, referenceBlockHash } + ); + if (!value) { + throw new Error('unpected null value'); + } + res.send( + value.map(v => ( + { + utxo_id: v.utxoId, + tx_hash: v.txHash, + tx_index: v.txIndex, + block_num: v.blockNum, + receiver: v.receiver, + amount: v.amount, + assets: v.assets, + } + )) + ); + }); + + server.post('/api/v2/txs/utxoDiffSincePoint', async (req, res) => { + const { addresses, untilBlockHash, afterBestBlock } = req.body; + const { result, value } = await mockImporter.mockUtxoApi.getUtxoDiffSincePoint( + { addresses, untilBlockHash, afterBestBlock } + ); + if (result !== 'SUCCESS') { + res.status(500); + res.send({ error: { response: 'REFERENCE_POINT_BLOCK_NOT_FOUND' } }); + } else { + if (!value) { + throw new Error('unpected null value'); + } + // casting `value` to `any` is the only way to pass flow check + const diffItems = (value: any).diffItems.map(item => { + if (item.type === 'input') { + return item; + } + return { + type: 'output', + id: item.utxo.utxoId, + receiver: item.utxo.receiver, + amount: item.utxo.amount, + assets: item.utxo.assets, + block_num: item.utxo.blockNum, + tx_hash: item.utxo.txHash, + tx_index: item.utxo.txIndex, + }; + }); + res.send({ diffItems }); + } + }); + installCoinPriceRequestHandlers(server); MockServer = server.listen(Ports.DevBackendServe, () => { From c68915dc1dc05f908365ca47407fbf130b92316b Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 25 Jul 2022 23:51:40 +0300 Subject: [PATCH 021/199] Updated page elements --- .../features/pages/createWalletPage.js | 67 +++++++++++++++++++ .../features/pages/newWalletPages.js | 11 +++ .../features/pages/restoreWalletPage.js | 40 +++++++++-- 3 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 packages/yoroi-extension/features/pages/createWalletPage.js diff --git a/packages/yoroi-extension/features/pages/createWalletPage.js b/packages/yoroi-extension/features/pages/createWalletPage.js new file mode 100644 index 0000000000..f40bf517a6 --- /dev/null +++ b/packages/yoroi-extension/features/pages/createWalletPage.js @@ -0,0 +1,67 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const walletInfoDialog: LocatorObject = { locator: '.WalletCreateDialog', method: 'css' }; +export const creationConfirmButton: LocatorObject = { + locator: '.WalletCreateDialog .primary', + method: 'css', +}; +export const backupPrivacyWarningDialog: LocatorObject = { + locator: '.WalletBackupPrivacyWarningDialog', + method: 'css', +}; +export const creationWarningContinueButton: LocatorObject = { + locator: '.WalletBackupPrivacyWarningDialog .primary', + method: 'css', +}; +export const nobodyLooksCheckbox: LocatorObject = { + locator: '.MuiFormControlLabel-root', + method: 'css', +}; +export const walletRecoveryPhraseDisplayDialog: LocatorObject = { + locator: '.WalletRecoveryPhraseDisplayDialog', + method: 'css', +}; +export const mnemonicPhraseText: LocatorObject = { + locator: '.WalletRecoveryPhraseMnemonic_component', + method: 'css', +}; +export const iWrittenDownButton: LocatorObject = { + locator: '.WalletRecoveryPhraseDisplayDialog .primary', + method: 'css', +}; +export const recoveryPhraseEntryDialog: LocatorObject = { + locator: '.WalletRecoveryPhraseEntryDialog', + method: 'css', +}; +const recoveryPhraseWord: LocatorObject = { + locator: '.WalletRecoveryPhraseEntryDialog_words .MnemonicWord_component', + method: 'css', +}; +export const repeatRecoveryPhrase = async ( + customWorld: any, + mnemonicPhrase: string +): Promise => { + const mnemonicPhraseSplit = mnemonicPhrase.split(' '); + for (const mnemonicPhraseWord of mnemonicPhraseSplit) { + const allWords = await customWorld.findElements(recoveryPhraseWord); + for (const wordElement of allWords) { + const elementText = await wordElement.getText(); + if (elementText !== mnemonicPhraseWord) continue; + await wordElement.click(); + } + } + await customWorld.driver.sleep(500); +}; + +const recoveryPhraseEntryDialogCheckboxes: LocatorObject = { locator: '.WalletRecoveryPhraseEntryDialog_checkbox', method: 'css' }; + +export const checkRecoveryPhrase2Checkboxes = async (customWorld: any) => { + const allCheckboxes = await customWorld.findElements(recoveryPhraseEntryDialogCheckboxes); + for (const checkbox of allCheckboxes) { + await checkbox.click(); + } +} + +export const recoveryPhraseEntryDialogConfirmButton: LocatorObject = { locator: '.WalletRecoveryPhraseEntryDialog .primary', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/newWalletPages.js b/packages/yoroi-extension/features/pages/newWalletPages.js index 27a5240574..a6e05734eb 100644 --- a/packages/yoroi-extension/features/pages/newWalletPages.js +++ b/packages/yoroi-extension/features/pages/newWalletPages.js @@ -10,14 +10,25 @@ export const pickUpCurrencyDialog: LocatorObject = { locator: '.PickCurrencyOpti export const getCurrencyButton = (currency: string): LocatorObject => { return { locator: `.PickCurrencyOptionDialog_${currency}`, method: 'css' }; }; +// Create options dialog +export const createOptionDialog: LocatorObject = { locator: '.WalletCreateOptionDialog', method: 'css' }; +export const createNormalWalletButton: LocatorObject = { locator: '.WalletCreateOptionDialog_createWallet', method: 'css' }; +export const createPaperWalletButton: LocatorObject = { locator: '.WalletCreateOptionDialog_restorePaperWallet', method: 'css' }; + +// Restore options dialog +export const restoreOptionDialog: LocatorObject = { locator: '.WalletRestoreOptionDialog', method: 'css' }; +export const normalWordWalletButton: LocatorObject = { locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }; + // HW options dialog export const hwOptionsDialog: LocatorObject = { locator: '.WalletConnectHWOptionDialog', method: 'css' }; export const ledgerWalletButton: LocatorObject = { locator: '.WalletConnectHWOptionDialog_connectLedger', method: 'css' }; export const trezorWalletButton: LocatorObject = { locator: '.WalletConnectHWOptionDialog_connectTrezor', method: 'css' }; + // Era options dialog export const eraOptionsDialog: LocatorObject = { locator: '.WalletEraOptionDialog', method: 'css' }; export const shelleyEraButton: LocatorObject = { locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }; export const byronEraButton: LocatorObject = { locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }; + // Trezor connect dialog export const trezorConnectDialog: LocatorObject = { locator: '.CheckDialog', method: 'css' }; export const trezorWalletName: LocatorObject = { locator: '//input[@name="walletName"]', method: 'xpath' }; diff --git a/packages/yoroi-extension/features/pages/restoreWalletPage.js b/packages/yoroi-extension/features/pages/restoreWalletPage.js index 1763940354..5c1bbb6a0f 100644 --- a/packages/yoroi-extension/features/pages/restoreWalletPage.js +++ b/packages/yoroi-extension/features/pages/restoreWalletPage.js @@ -1,23 +1,27 @@ // @flow import type { LocatorObject } from '../support/webdriver'; +import type { RestorationInput } from '../mock-chain/TestWallets'; +import { Key } from 'selenium-webdriver'; -export const errorInvalidRecoveryPhrase = { +export const restoreWalletInputPhraseDialog: LocatorObject = { locator: '.WalletRestoreDialog', method: 'css' }; + +export const errorInvalidRecoveryPhrase: LocatorObject = { locator: '//p[contains(@class, "-error") and contains(@id, "recoveryPhrase")]', method: 'xpath' }; -export const recoveryPhraseField = { +export const recoveryPhraseField: LocatorObject = { locator: '//input[starts-with(@id, "downshift-") and contains(@id, "-input")]', method: 'xpath' }; -export const proceedRecoveryButton = { +export const proceedRecoveryButton: LocatorObject = { locator: 'primaryButton', method: 'id' }; -export const cleanRecoverInput = { +export const cleanRecoverInput: LocatorObject = { locator: '.AutocompleteOverridesClassic_autocompleteWrapper input', method: 'css' }; @@ -26,6 +30,28 @@ export const getWords = (word: string): LocatorObject => { return { locator: `//span[contains(text(), '${word}')]`, method: 'xpath' } }; -export const walletPasswordInput = { locator: "input[name='walletPassword']", method: 'css' }; -export const repeatPasswordInput = { locator: "input[name='repeatPassword']", method: 'css' }; -export const paperPasswordInput = { locator: "input[name='paperPassword']", method: 'css' }; \ No newline at end of file +export const walletNameInput: LocatorObject = { locator: "input[name='walletName']", method: 'css' }; +export const walletPasswordInput: LocatorObject = { locator: "input[name='walletPassword']", method: 'css' }; +export const repeatPasswordInput: LocatorObject = { locator: "input[name='repeatPassword']", method: 'css' }; +export const paperPasswordInput: LocatorObject = { locator: "input[name='paperPassword']", method: 'css' }; +export const confirmConfirmationButton: LocatorObject = { locator: '.WalletRestoreDialog .primary', method: 'css' }; + +export const enterRecoveryPhrase = async (customWorld: any, phrase: string) => { + const recoveryPhrase = phrase.split(' '); + for (let i = 0; i < recoveryPhrase.length; i++) { + const recoveryPhraseElement = await customWorld.findElement(recoveryPhraseField); + await recoveryPhraseElement.sendKeys(recoveryPhrase[i], Key.RETURN); + if (i === 0) await customWorld.driver.sleep(500); + } +} + +export const inputMnemonicForWallet = async ( + customWorld: any, + restoreInfo: RestorationInput +): Promise => { + await customWorld.input(walletNameInput, restoreInfo.name); + await enterRecoveryPhrase(customWorld, restoreInfo.mnemonic); + await customWorld.input(walletPasswordInput, restoreInfo.password); + await customWorld.input(repeatPasswordInput, restoreInfo.password); + await customWorld.click(confirmConfirmationButton); +} \ No newline at end of file From cbeb96150b71259960235a56fc448247240d65f1 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 25 Jul 2022 23:52:36 +0300 Subject: [PATCH 022/199] Removed the enterRecoveryPhrase from the helpers.js --- .../features/support/helpers/helpers.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/yoroi-extension/features/support/helpers/helpers.js b/packages/yoroi-extension/features/support/helpers/helpers.js index 2ed793e547..dfacf8277e 100644 --- a/packages/yoroi-extension/features/support/helpers/helpers.js +++ b/packages/yoroi-extension/features/support/helpers/helpers.js @@ -1,7 +1,6 @@ // @flow -import { By, Key } from 'selenium-webdriver'; -import { recoveryPhraseField } from '../../pages/restoreWalletPage'; +import { By } from 'selenium-webdriver'; export const checkIfElementsInArrayAreUnique = function (arr: Array): boolean { return new Set(arr).size === arr.length; @@ -41,16 +40,6 @@ export function getMethod( } } -export async function enterRecoveryPhrase(customWorld: any, phrase: string) { - - const recoveryPhrase = phrase.split(' '); - for (let i = 0; i < recoveryPhrase.length; i++) { - const recoveryPhraseElement = await customWorld.findElement(recoveryPhraseField); - await recoveryPhraseElement.sendKeys(recoveryPhrase[i], Key.RETURN); - if (i === 0) await customWorld.driver.sleep(500); - } -} - export function getLogDate(): string { return new Date().toISOString().replace(/:/g, '_'); } From 23a0bd99139e83eedbde19bee95dc8cbc8693872 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 25 Jul 2022 23:53:17 +0300 Subject: [PATCH 023/199] Moved the test common password to the common-constants.js --- .../features/mock-chain/TestWallets.js | 18 ++++++++++++++++-- .../support/helpers/common-constants.js | 2 ++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/features/mock-chain/TestWallets.js b/packages/yoroi-extension/features/mock-chain/TestWallets.js index fc3dc0f87f..f03584978e 100644 --- a/packages/yoroi-extension/features/mock-chain/TestWallets.js +++ b/packages/yoroi-extension/features/mock-chain/TestWallets.js @@ -1,5 +1,7 @@ // @flow +import { commonWalletPassword } from '../support/helpers/common-constants'; + export type RestorationInput = {| name: string, password: string, @@ -20,7 +22,7 @@ function createWallet(payload: {| name, mnemonic, plate, - password: 'asdfasdfasdf', + password: commonWalletPassword, deviceId: payload.deviceId, } }; } @@ -48,7 +50,9 @@ type WalletNames = 'shelley-enterprise' | 'shelley-mangled' | 'ergo-token-wallet' | - 'cardano-token-wallet'; + 'cardano-token-wallet' | + 'First-Smoke-Test-Wallet' | + 'Second-Smoke-Test-Wallet'; // eslint-disable-next-line prefer-object-spread export const testWallets: { [key: WalletNames]: RestorationInput, ... } = Object.assign( @@ -161,4 +165,14 @@ export const testWallets: { [key: WalletNames]: RestorationInput, ... } = Object mnemonic: 'rent sword help dynamic enhance collect biology drama agent raven grape bike march length leisure', plate: 'HZPX-1482', }), + createWallet({ + name: ('First-Smoke-Test-Wallet': WalletNames), + mnemonic: process.env.FIRST_SMOKE_TEST_WALLET, + plate: 'XONT-4910' + }), + createWallet({ + name: ('Second-Smoke-Test-Wallet': WalletNames), + mnemonic: process.env.SECOND_SMOKE_TEST_WALLET, + plate: 'XZHD-1651', + }), ); diff --git a/packages/yoroi-extension/features/support/helpers/common-constants.js b/packages/yoroi-extension/features/support/helpers/common-constants.js index 19ed3b4ccc..77d8e68c01 100644 --- a/packages/yoroi-extension/features/support/helpers/common-constants.js +++ b/packages/yoroi-extension/features/support/helpers/common-constants.js @@ -13,3 +13,5 @@ export const emailOptions = { url: `https://mailsac.com/api/addresses/${mailsacEmail}/messages`, headers: { 'Mailsac-Key': mailsacAPIKey }, }; + +export const commonWalletPassword = 'asdfasdfasdf'; \ No newline at end of file From 1336d8c22c3c2d529af0f0c81d43a988763243f2 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 25 Jul 2022 23:56:28 +0300 Subject: [PATCH 024/199] Added creating a Shelley era wallet step. Updated the code a bit. --- .../features/step_definitions/common-steps.js | 140 ++++++++++++------ 1 file changed, 96 insertions(+), 44 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 2f0999da7e..16874c7ff1 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -13,17 +13,17 @@ import { import * as CardanoServer from '../mock-chain/mockCardanoServer'; import * as ErgoServer from '../mock-chain/mockErgoServer'; import { By, logging } from 'selenium-webdriver'; -import { enterRecoveryPhrase, getLogDate } from '../support/helpers/helpers'; +import { getLogDate } from '../support/helpers/helpers'; import { testWallets } from '../mock-chain/TestWallets'; import * as ErgoImporter from '../mock-chain/mockErgoImporter'; import * as CardanoImporter from '../mock-chain/mockCardanoImporter'; import { testRunsDataDir, snapshotsDir, - } from '../support/helpers/common-constants'; + commonWalletPassword, +} from '../support/helpers/common-constants'; import { expect } from 'chai'; import { satisfies } from 'semver'; -// eslint-disable-next-line import/named import { truncateLongName } from '../../app/utils/formatters'; import stableStringify from 'json-stable-stringify'; import type { RestorationInput } from '../mock-chain/TestWallets'; @@ -50,9 +50,34 @@ import { trezorConfirmButton, walletNameInput, saveDialog, - saveButton + saveButton, + restoreOptionDialog, + normalWordWalletButton, + byronEraButton, + createWalletButton, + createOptionDialog, + createNormalWalletButton, } from '../pages/newWalletPages'; import { allowPubKeysAndSwitchToYoroi, switchToTrezorAndAllow } from './trezor-steps'; +import { + restoreWalletInputPhraseDialog, + inputMnemonicForWallet, + walletPasswordInput, + repeatPasswordInput, +} from '../pages/restoreWalletPage'; +import { + backupPrivacyWarningDialog, + checkRecoveryPhrase2Checkboxes, + creationConfirmButton, + creationWarningContinueButton, + iWrittenDownButton, + mnemonicPhraseText, + nobodyLooksCheckbox, + recoveryPhraseEntryDialog, recoveryPhraseEntryDialogConfirmButton, + repeatRecoveryPhrase, + walletInfoDialog, + walletRecoveryPhraseDisplayDialog +} from '../pages/createWalletPage'; const { promisify } = require('util'); const fs = require('fs'); @@ -159,6 +184,12 @@ After(async function (scenario) { await this.driver.quit(); }); +type WalletEra = {| + era: + | 'shelley' + | 'byron', +|} + export async function getPlates(customWorld: any): Promise { // check plate in confirmation dialog let plateElements = await customWorld.driver.findElements( @@ -252,23 +283,40 @@ async function getLogs(driver, name, loggingType) { await fsAsync.writeFile(consoleLogPath, JSON.stringify(jsonLogs)); } -async function inputMnemonicForWallet( +async function restoreWallet ( + customWorld: any, + walletEra: WalletEra, + walletName: string +): Promise { + const restoreInfo = testWallets[walletName]; + expect(restoreInfo).to.not.equal(undefined); + + await customWorld.click(restoreWalletButton); + + await customWorld.waitForElement(pickUpCurrencyDialog); + await customWorld.click(getCurrencyButton('cardano')); + + await customWorld.waitForElement(restoreOptionDialog); + + await customWorld.click(normalWordWalletButton); + if (walletEra === 'shelley') { + await customWorld.click(shelleyEraButton); + } else if (walletEra === 'byron') { + await customWorld.click(byronEraButton); + } else { + throw new Error(`Unknown wallet era: ${walletEra}.`); + } + await customWorld.waitForElement(restoreWalletInputPhraseDialog); + + await inputMnemonicForWallet(customWorld, restoreInfo); + await checkWalletPlate(customWorld, walletName, restoreInfo); +} + +async function checkWalletPlate( customWorld: any, walletName: string, restoreInfo: RestorationInput ): Promise { - await customWorld.input({ locator: "input[name='walletName']", method: 'css' }, restoreInfo.name); - await enterRecoveryPhrase(customWorld, restoreInfo.mnemonic); - await customWorld.input( - { locator: "input[name='walletPassword']", method: 'css' }, - restoreInfo.password - ); - await customWorld.input( - { locator: "input[name='repeatPassword']", method: 'css' }, - restoreInfo.password - ); - await customWorld.click({ locator: '.WalletRestoreDialog .primary', method: 'css' }); - const plateElements = await getPlates(customWorld); const plateText = await plateElements[0].getText(); expect(plateText).to.be.equal(restoreInfo.plate); @@ -308,46 +356,50 @@ Given(/^There is an Ergo wallet stored named ([^"]*)$/, async function (walletNa await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); - await inputMnemonicForWallet(this, walletName, restoreInfo); + await inputMnemonicForWallet(this, restoreInfo); + await checkWalletPlate(this, walletName, restoreInfo); }); Given(/^There is a Shelley wallet stored named ([^"]*)$/, async function (walletName) { this.webDriverLogger.info(`Step: There is a Shelley wallet stored named ${walletName}`); - const restoreInfo = testWallets[walletName]; - expect(restoreInfo).to.not.equal(undefined); - - await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); - - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); - - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); - - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); - - await inputMnemonicForWallet(this, walletName, restoreInfo); + await restoreWallet(this, 'shelley', walletName); }); Given(/^There is a Byron wallet stored named ([^"]*)$/, async function (walletName) { this.webDriverLogger.info(`Step: There is a Byron wallet stored named ${walletName}`); - const restoreInfo = testWallets[walletName]; - expect(restoreInfo).to.not.equal(undefined); + await restoreWallet(this, 'byron', walletName); +}); - await this.click(restoreWalletButton); +Given(/^I create a new Shelley wallet with the name ([^"]*)$/, async function (walletName) { + await this.click(createWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(getCurrencyButton('cardano')); - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); - - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); - - await inputMnemonicForWallet(this, walletName, restoreInfo); -}); + await this.waitForElement(createOptionDialog); + await this.click(createNormalWalletButton); + + await this.waitForElement(walletInfoDialog); + await this.input(walletNameInput, walletName); + await this.input(walletPasswordInput, commonWalletPassword); + await this.input(repeatPasswordInput, commonWalletPassword); + await this.click(creationConfirmButton); + + await this.waitForElement(backupPrivacyWarningDialog); + await this.click(nobodyLooksCheckbox); + await this.waitEnable(creationWarningContinueButton); + await this.click(creationWarningContinueButton); + + await this.waitForElement(walletRecoveryPhraseDisplayDialog); + const rawMnemonicPhrase = (await this.getText(mnemonicPhraseText)).trim(); + await this.click(iWrittenDownButton); + + // enter recovery phrase + await this.waitForElement(recoveryPhraseEntryDialog); + await repeatRecoveryPhrase(this, rawMnemonicPhrase); + await checkRecoveryPhrase2Checkboxes(this); + await this.click(recoveryPhraseEntryDialogConfirmButton); +}) Given(/^I have completed the basic setup$/, async function () { this.webDriverLogger.info(`Step: I have completed the basic setup`); From 503b20018435e5cbbe4f2d4621f790287d598729 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 25 Jul 2022 23:56:56 +0300 Subject: [PATCH 025/199] Added 2 smoke tests --- .../yoroi-extension/features/smoke.feature | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 packages/yoroi-extension/features/smoke.feature diff --git a/packages/yoroi-extension/features/smoke.feature b/packages/yoroi-extension/features/smoke.feature new file mode 100644 index 0000000000..574d656732 --- /dev/null +++ b/packages/yoroi-extension/features/smoke.feature @@ -0,0 +1,28 @@ +Feature: Smoke tests + + Background: + Given I have opened the extension + And I have completed the basic setup + + @smoke-001 + Scenario: Create a Shelley Era wallet + Given I create a new Shelley wallet with the name Test Wallet + Then I should see the balance number "0 ADA" + When I go to the receive screen + Then I should see the Receive screen + And I should see at least 1 addresses + + @smoke-002 + Scenario: Restore a Shelley Era wallet (smoke-001) + Given There is a Shelley wallet stored named First-Smoke-Test-Wallet + # Check the balance on the main page + Then I should see the balance number "0 ADA" + # Check transactions + Then I click the transaction page button + And I should see the summary screen + And I should see no transactions +# And I should see 1 successful transactions + # Check the Receive tab + When I go to the receive screen + Then I should see the Receive screen + And I should see at least 1 addresses \ No newline at end of file From 7b965e2800c7f6f314f5c059c75b7dfa54921d15 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 25 Jul 2022 23:57:19 +0300 Subject: [PATCH 026/199] Locator object update --- .../features/step_definitions/transactions-steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index a63f21e1ed..f76b9403d0 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -129,7 +129,7 @@ Then(/^I should see the successfully sent page$/, async function () { }); Then(/^I click the transaction page button$/, async function () { - await this.click({ locator: "//button[contains(text(), 'Transaction page')]", method: 'xpath' }); + await this.click({ locator: '.summary', method: 'css' }); }); Then(/^I should see the summary screen$/, async function () { From 4437ff90dc0782ae4d5d0c5dba9e239e612ab607 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 13:51:55 +0300 Subject: [PATCH 027/199] added id for tests --- .../app/components/wallet/summary/WalletSummary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/components/wallet/summary/WalletSummary.js b/packages/yoroi-extension/app/components/wallet/summary/WalletSummary.js index f3a0adea89..88f68d91e6 100644 --- a/packages/yoroi-extension/app/components/wallet/summary/WalletSummary.js +++ b/packages/yoroi-extension/app/components/wallet/summary/WalletSummary.js @@ -204,7 +204,7 @@ export default class WalletSummary extends Component { const { intl } = this.context; const content = ( -
    +
    From d6dbad76dee0c409a86504796f5fc42d558a23bb Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 13:53:13 +0300 Subject: [PATCH 028/199] Added a separate Before hook specially for smoke test --- .../yoroi-extension/features/step_definitions/common-steps.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 16874c7ff1..c87bad5641 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -171,6 +171,10 @@ After({ tags: '@trezorEmulatorTest' }, async function () { this.trezorController.closeWsConnection(); }); +Before({ tags: '@smoke' }, () => { + setDefaultTimeout(3 * 60 * 1000); +}); + After(async function (scenario) { this.sendToAllLoggers(`#### The scenario "${scenario.pickle.name}" has done ####`); if (scenario.result.status === 'failed') { From 67c968b1312d869b3284673b2b4e54302659aa9d Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 13:53:58 +0300 Subject: [PATCH 029/199] Added the new page object dashboardPage.js --- .../features/pages/dashboardPage.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 packages/yoroi-extension/features/pages/dashboardPage.js diff --git a/packages/yoroi-extension/features/pages/dashboardPage.js b/packages/yoroi-extension/features/pages/dashboardPage.js new file mode 100644 index 0000000000..e7ceaad61f --- /dev/null +++ b/packages/yoroi-extension/features/pages/dashboardPage.js @@ -0,0 +1,16 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const cardValueText: LocatorObject = { locator: '.UserSummary_value', method: 'css' }; +export const getTotalAdaValue = async (customWorld: any): Promise => { + const cardsElements = await customWorld.findElements(cardValueText) + const totalValueText = await cardsElements[0].getText() + return parseFloat((totalValueText).split(' ')[0]) +} + +export const getRewardValue = async (customWorld: any): Promise => { + const cardsElements = await customWorld.findElements(cardValueText) + const rewardValueText = await cardsElements[1].getText() + return parseFloat((rewardValueText).split(' ')[0]) +} \ No newline at end of file From a971a0f678e023f9ad9cc7895580d2b8d80b3c74 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 13:55:10 +0300 Subject: [PATCH 030/199] Reworked old steps --- .../features/step_definitions/main-ui-steps.js | 11 +++++++++++ .../step_definitions/transactions-steps.js | 18 ++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/main-ui-steps.js b/packages/yoroi-extension/features/step_definitions/main-ui-steps.js index c10d86de3c..e253224fbc 100644 --- a/packages/yoroi-extension/features/step_definitions/main-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/main-ui-steps.js @@ -5,11 +5,22 @@ import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import { hiddenAmount } from '../../app/utils/strings'; import { truncateAddress, } from '../../app/utils/formatters'; +import { getRewardValue, getTotalAdaValue } from '../pages/dashboardPage'; Then(/^I should see the balance number "([^"]*)"$/, async function (number) { await this.waitUntilText({ locator: '.NavWalletDetails_amount', method: 'css' }, number); }); +Then(/^I should see the Total ADA is equal to "([^"]*)"$/, async function (expectedTotalAda) { + const realTotalAda = await getTotalAdaValue(this); + expect(parseFloat(expectedTotalAda), `The Total ADA is different from the expected`).to.be.equal(realTotalAda); +}) + +Then(/^I should see the Reward is equal to "([^"]*)"$/, async function (expectedRewardAmount) { + const realRewardAmount = await getRewardValue(this); + expect(parseFloat(expectedRewardAmount), `The Total ADA is different from the expected`).to.be.equal(realRewardAmount); +}) + Then(/^I should see send transaction screen$/, async function () { await this.waitForElement({ locator: "input[name='receiver']", method: 'css' }); await this.waitForElement({ locator: "input[name='amount']", method: 'css' }); diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index f76b9403d0..685870b3ab 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -11,16 +11,18 @@ import { networks, defaultAssets, } from '../../app/api/ada/lib/storage/database/prepackaged/networks'; -import { walletSummaryBox } from '../pages/walletTransactionsPage'; +import { walletSummaryBox } from '../pages/walletTransactionsHistoryPage'; Given(/^I have a wallet with funds$/, async function () { - const amountWithCurrency = await this.driver.findElements( - By.xpath("//div[@class='WalletTopbarTitle_walletAmount']") + await this.waitUntilContainsText( + { locator: '.NavWalletDetails_amount', method: 'css' }, + 'ADA', + 60 * 1000 ); - const matchedAmount = /^"([0-9]*\.[0-9]*)".*$/.exec(amountWithCurrency); - if (!matchedAmount) return false; - const amount = parseFloat(matchedAmount[1]); - expect(Number(amount), 'Available funds').to.be.above(0); + const balanceTextElement = await this.findElement({ locator: '.NavWalletDetails_amount', method: 'css' }); + const balanceText = await balanceTextElement.getText(); + const [balance, ] = balanceText.split(' '); + expect(parseFloat(balance, 10), 'The wallet is empty').to.be.above(0); }); When(/^I go to the send transaction screen$/, async function () { @@ -129,7 +131,7 @@ Then(/^I should see the successfully sent page$/, async function () { }); Then(/^I click the transaction page button$/, async function () { - await this.click({ locator: '.summary', method: 'css' }); + await this.click({ locator: '//button[contains(text(), "Transaction page")]', method: 'xpath' }); }); Then(/^I should see the summary screen$/, async function () { From 44723997b92cc406ad8b53643a7c6fc17a652a23 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 13:57:35 +0300 Subject: [PATCH 031/199] Using expect instead of chai.expect --- .../step_definitions/tx-history-steps.js | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js index 02ea1952b2..bb277c1be3 100644 --- a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js +++ b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js @@ -2,7 +2,7 @@ import { Then, When, Given } from 'cucumber'; import { By } from 'selenium-webdriver'; -import chai from 'chai'; +import { expect, AssertionError } from 'chai'; import moment from 'moment'; import i18n from '../support/helpers/i18n-helpers'; @@ -10,26 +10,26 @@ function verifyAllTxsFields( txType, txAmount, txTime, txStatus, txFee, txFromList, txToList, txId, expectedTx, txConfirmations ) { - chai.expect(txType).to.equal(expectedTx.txType); - chai.expect(txAmount.split(' ')[0]).to.equal(expectedTx.txAmount); - chai.expect(txTime).to.equal(moment(expectedTx.txTime).format('hh:mm:ss A')); - chai.expect(txStatus).to.equal(expectedTx.txStatus); + expect(txType).to.equal(expectedTx.txType); + expect(txAmount.split(' ')[0]).to.equal(expectedTx.txAmount); + expect(txTime).to.equal(moment(expectedTx.txTime).format('hh:mm:ss A')); + expect(txStatus).to.equal(expectedTx.txStatus); for (let i = 0; i < txFromList.length; i++) { for (let j = 0; j < txFromList[i].length; j++) { - chai.expect(txFromList[i][j]).to.equal(expectedTx.txFrom[i][j]); + expect(txFromList[i][j]).to.equal(expectedTx.txFrom[i][j]); } } for (let i = 0; i < txToList.length; i++) { for (let j = 0; j < txToList[i].length; j++) { - chai.expect(txToList[i][j]).to.equal(expectedTx.txTo[i][j]); + expect(txToList[i][j]).to.equal(expectedTx.txTo[i][j]); } } - chai.expect(txId).to.equal(expectedTx.txId); + expect(txId).to.equal(expectedTx.txId); if (txConfirmations) { - chai.expect(txConfirmations).to.equal(expectedTx.txConfirmations); + expect(txConfirmations).to.equal(expectedTx.txConfirmations); } if (txFee) { - chai.expect(txFee).to.equal(expectedTx.txFee); + expect(txFee).to.equal(expectedTx.txFee); } } @@ -59,8 +59,8 @@ Then( Then(/^I should see no transactions$/, async function () { await this.waitForElement({ locator: '.WalletNoTransactions_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); - chai.expect(actualTxsList.length).to.equal(0); + const actualTxsList = await this.getElementsBy(transactionComponent); + expect(actualTxsList.length).to.equal(0); }); Then( @@ -80,18 +80,20 @@ Then( await this.driver.sleep(500); } - const allTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); - const pendingTxsList = await this.getElementsBy({ locator: '.Transaction_pendingLabel', method: 'css' }); - const failedTxsList = await this.getElementsBy({ locator: '.Transaction_failedLabel', method: 'css' }); if (txExpectedStatus === 'pending') { - chai.expect(pendingTxsList.length).to.equal(txsAmount); + const pendingTxsList = await this.getElementsBy({ locator: '.Transaction_pendingLabel', method: 'css' }); + expect(pendingTxsList.length).to.equal(txsAmount); return; } if (txExpectedStatus === 'failed') { - chai.expect(failedTxsList.length).to.equal(txsAmount); + const failedTxsList = await this.getElementsBy({ locator: '.Transaction_failedLabel', method: 'css' }); + expect(failedTxsList.length).to.equal(txsAmount); return; } - chai.expect(allTxsList.length - pendingTxsList.length - failedTxsList.length) + const pendingTxsList = await this.getElementsBy({ locator: '.Transaction_pendingLabel', method: 'css' }); + const failedTxsList = await this.getElementsBy({ locator: '.Transaction_failedLabel', method: 'css' }); + const allTxsList = await this.getElementsBy(transactionComponent); + expect(allTxsList.length - pendingTxsList.length - failedTxsList.length) .to.equal(txsAmount); } ); @@ -99,8 +101,8 @@ Then( When( /^I expand the top transaction$/, async function () { - await this.waitForElement({ locator: '.Transaction_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); + await this.waitForElement(transactionComponent); + const actualTxsList = await this.getElementsBy(transactionComponent); const topTx = actualTxsList[0]; await topTx.click(); @@ -123,8 +125,8 @@ async function parseTxInfo(addressList) { Then( /^I verify top transaction content ([^"]*)$/, async function (walletName) { - await this.waitForElement({ locator: '.Transaction_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); + await this.waitForElement(transactionComponent); + const actualTxsList = await this.getElementsBy(transactionComponent); const topTx = actualTxsList[0]; let status = 'successful'; @@ -171,12 +173,12 @@ Then( Then( /^The number of confirmations of the top tx is ([^"]*)$/, async function (count) { - await this.waitForElement({ locator: '.Transaction_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); + await this.waitForElement(transactionComponent); + const actualTxsList = await this.getElementsBy(transactionComponent); const topTx = actualTxsList[0]; const assuranceElem = await topTx.findElements(By.css('.confirmationCount')); const confirmationCount = await assuranceElem[0].getText(); - chai.expect(confirmationCount).to.equal(count); + expect(confirmationCount).to.equal(count); } ); From 726769db467442c31d73e51074d556cb1bb0abb2 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 13:58:50 +0300 Subject: [PATCH 032/199] Added the step for waiting till a transaction is confirmed --- .../step_definitions/tx-history-steps.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js index bb277c1be3..7b0b0545d3 100644 --- a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js +++ b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js @@ -5,6 +5,8 @@ import { By } from 'selenium-webdriver'; import { expect, AssertionError } from 'chai'; import moment from 'moment'; import i18n from '../support/helpers/i18n-helpers'; +import { getTopTx, getTxStatus, transactionComponent } from '../pages/walletTransactionsHistoryPage'; +import { txSuccessfulStatuses } from '../support/helpers/common-constants'; function verifyAllTxsFields( txType, txAmount, txTime, txStatus, txFee, txFromList, txToList, @@ -233,3 +235,22 @@ const displayInfo = { When(/^I go to the tx history screen$/, async function () { await this.click({ locator: '.summary ', method: 'css' }); }); + +Then(/^I wait for (\d+) minute\(s\) the last transaction is confirmed$/, async function (minutes) { + const waitTimeMs = parseInt(minutes, 10) * 60 * 1000; + this.webDriverLogger.info(`Step: I wait for ${minutes} minute(s) the last transaction is confirmed`); + const startTime = Date.now(); + while (startTime + waitTimeMs > Date.now()){ + const topTx = await getTopTx(this); + const topTxState = await getTxStatus(topTx); + if(txSuccessfulStatuses.includes(topTxState.toLowerCase())){ + const endTime = Date.now(); + this.webDriverLogger.debug(`Step: The new transaction is confirmed`); + this.webDriverLogger.debug(`Step: Waiting time is ${(endTime - startTime) / 1000} seconds`); + return; + } + await this.driver.sleep(1000); + } + this.webDriverLogger.error(`The latest transaction is still in status "Submitted" after ${minutes} minutes`); + throw new AssertionError(`The latest transaction is still in status "Submitted" after ${minutes} minutes`); +}); \ No newline at end of file From 4c227eb37c71a5f9e9ccb7305591c34341f2a234 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 13:59:23 +0300 Subject: [PATCH 033/199] Renamed walletTransactionsPage.js -> walletTransactionsHistoryPage.js --- .../pages/walletTransactionsHistoryPage.js | 23 +++++++++++++++++++ .../features/pages/walletTransactionsPage.js | 3 --- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js delete mode 100644 packages/yoroi-extension/features/pages/walletTransactionsPage.js diff --git a/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js b/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js new file mode 100644 index 0000000000..d5238f9fe3 --- /dev/null +++ b/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js @@ -0,0 +1,23 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; +import { getMethod } from '../support/helpers/helpers'; + +export const walletSummaryBox: LocatorObject = { locator: 'walletSummary_box', method: 'id' }; +export const transactionComponent: LocatorObject = { + locator: '.Transaction_component', + method: 'css', +}; +export const transactionStatus: LocatorObject = { locator: '.Transaction_status', method: 'css' }; + +export const getTopTx = async (customWorld: any): Promise => { + const actualTxsList = await customWorld.getElementsBy(transactionComponent); + return actualTxsList[0]; +}; + +export const getTxStatus = async (tx: webdriver$WebElement): Promise => { + const statusElement = await tx.findElement( + getMethod(transactionStatus.method)(transactionStatus.locator) + ); + return await statusElement.getText(); +}; diff --git a/packages/yoroi-extension/features/pages/walletTransactionsPage.js b/packages/yoroi-extension/features/pages/walletTransactionsPage.js deleted file mode 100644 index 83ac150032..0000000000 --- a/packages/yoroi-extension/features/pages/walletTransactionsPage.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow - -export const walletSummaryBox = { locator: 'walletSummary_box', method: 'id' } \ No newline at end of file From 14e3a7790daac24df74e134af35f4637dc1f18bf Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 14:03:56 +0300 Subject: [PATCH 034/199] removed an eslint comment --- packages/yoroi-extension/features/support/webdriver.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/yoroi-extension/features/support/webdriver.js b/packages/yoroi-extension/features/support/webdriver.js index 25bba311bb..73a9521c0d 100644 --- a/packages/yoroi-extension/features/support/webdriver.js +++ b/packages/yoroi-extension/features/support/webdriver.js @@ -5,7 +5,6 @@ import { Builder, Key, until, error, promise, WebElement } from 'selenium-webdri import chrome from 'selenium-webdriver/chrome'; import firefox from 'selenium-webdriver/firefox'; import path from 'path'; -// eslint-disable-next-line import/named import { RustModule } from '../../app/api/ada/lib/cardanoCrypto/rustLoader'; import { getMethod, getLogDate } from './helpers/helpers'; import { WindowManager } from './windowManager'; From 411c4d403db88eba16f901e5986fac2cb6aa84ad Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 14:10:12 +0300 Subject: [PATCH 035/199] Added the new test "Intrawallet transaction. 1 ADA" --- .../yoroi-extension/features/smoke.feature | 36 +++++++++++++++---- .../support/helpers/common-constants.js | 3 +- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/yoroi-extension/features/smoke.feature b/packages/yoroi-extension/features/smoke.feature index 574d656732..7ba7dcf9a9 100644 --- a/packages/yoroi-extension/features/smoke.feature +++ b/packages/yoroi-extension/features/smoke.feature @@ -1,3 +1,4 @@ +@smoke Feature: Smoke tests Background: @@ -5,7 +6,7 @@ Feature: Smoke tests And I have completed the basic setup @smoke-001 - Scenario: Create a Shelley Era wallet + Scenario: Create a Shelley Era wallet (smoke-001) Given I create a new Shelley wallet with the name Test Wallet Then I should see the balance number "0 ADA" When I go to the receive screen @@ -13,16 +14,37 @@ Feature: Smoke tests And I should see at least 1 addresses @smoke-002 - Scenario: Restore a Shelley Era wallet (smoke-001) + Scenario: Restore a Shelley Era wallet (smoke-002) Given There is a Shelley wallet stored named First-Smoke-Test-Wallet # Check the balance on the main page - Then I should see the balance number "0 ADA" + Then I should see the balance number "6.527639 ADA" + And I should see the Total ADA is equal to "6.527639" + And I should see the Reward is equal to "0" # Check transactions - Then I click the transaction page button + Then I go to the transaction history screen And I should see the summary screen - And I should see no transactions -# And I should see 1 successful transactions + And I should see 6 successful transactions # Check the Receive tab When I go to the receive screen Then I should see the Receive screen - And I should see at least 1 addresses \ No newline at end of file + And I should see at least 5 addresses + + @smoke-003 + Scenario: Sending intrawallet transaction. 1 ADA (smoke-003) + Given There is a Shelley wallet stored named Second-Smoke-Test-Wallet + And I have a wallet with funds + Then I go to the send transaction screen + And I fill the form: + | address | amount | + | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | 1 | + And I click on the next button in the wallet send form + And I see send money confirmation dialog + And I enter the wallet password: + | password | + | asdfasdfasdf | + And I submit the wallet send form + Then I should see the successfully sent page + And I click the transaction page button + Then I should see the summary screen + And I should see 1 pending transactions + Then I wait for 3 minute(s) the last transaction is confirmed \ No newline at end of file diff --git a/packages/yoroi-extension/features/support/helpers/common-constants.js b/packages/yoroi-extension/features/support/helpers/common-constants.js index 77d8e68c01..5af6b4a79d 100644 --- a/packages/yoroi-extension/features/support/helpers/common-constants.js +++ b/packages/yoroi-extension/features/support/helpers/common-constants.js @@ -14,4 +14,5 @@ export const emailOptions = { headers: { 'Mailsac-Key': mailsacAPIKey }, }; -export const commonWalletPassword = 'asdfasdfasdf'; \ No newline at end of file +export const commonWalletPassword = 'asdfasdfasdf'; +export const txSuccessfulStatuses = ['high', 'medium', 'low']; \ No newline at end of file From ceb0fdc58d162bbd8a87b9c5b18cd7bd73f9a4ff Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 16:53:04 +0300 Subject: [PATCH 036/199] Added ids for tests --- .../app/components/wallet/send/WalletSendForm.js | 4 ++-- .../app/components/widgets/tokenOption/TokenOptionRow.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendForm.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendForm.js index 36b15845b8..ac17e70ffc 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendForm.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendForm.js @@ -404,7 +404,7 @@ export default class WalletSendForm extends Component { return { label, value: token.value, - id: token.id + id: 'send-all' } }) ] @@ -544,7 +544,7 @@ export default class WalletSendForm extends Component { }} > {sendAmountOptions.map(option => ( - + ))} diff --git a/packages/yoroi-extension/app/components/widgets/tokenOption/TokenOptionRow.js b/packages/yoroi-extension/app/components/widgets/tokenOption/TokenOptionRow.js index 696c4e1b5a..729aa6f8ea 100644 --- a/packages/yoroi-extension/app/components/widgets/tokenOption/TokenOptionRow.js +++ b/packages/yoroi-extension/app/components/widgets/tokenOption/TokenOptionRow.js @@ -26,7 +26,7 @@ export default class TokenOptionRow extends Component { render(): Node { const notOnlyName = !this.props.nameOnly; return ( - + Date: Fri, 19 Aug 2022 16:53:47 +0300 Subject: [PATCH 037/199] Added walletSendPage.js --- .../features/pages/walletSendPage.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/yoroi-extension/features/pages/walletSendPage.js diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js new file mode 100644 index 0000000000..1a5dcec864 --- /dev/null +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -0,0 +1,20 @@ +// @flow + +import { truncateToken } from '../../app/utils/formatters'; +import type { LocatorObject } from '../support/webdriver'; + +export const selectAssetDropDown: LocatorObject = { + locator: '//div[starts-with(@id, "selectedToken--")]', + method: 'xpath', +}; +export const getTokenLocator = (tokenName: string): LocatorObject => { + return { locator: truncateToken(tokenName), method: 'id' }; +}; + +export const selectSendingAmountDropDown: LocatorObject = { + locator: '//div[starts-with(@id, "selectedAmount--")]', + method: 'xpath', +}; + +export const customAmountItem: LocatorObject = { locator: 'custom-amount', method: 'id' }; +export const sendAllItem: LocatorObject = { locator: 'send-all', method: 'id' }; From a281bdf80892ea223dadb2b1ed7981c3442ac0a3 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 16:55:56 +0300 Subject: [PATCH 038/199] Fixed steps --- .../step_definitions/transactions-steps.js | 29 +++++++------------ .../step_definitions/tx-history-steps.js | 4 +-- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index 685870b3ab..746f835f16 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -12,6 +12,12 @@ import { defaultAssets, } from '../../app/api/ada/lib/storage/database/prepackaged/networks'; import { walletSummaryBox } from '../pages/walletTransactionsHistoryPage'; +import { + getTokenLocator, + selectAssetDropDown, + selectSendingAmountDropDown, + sendAllItem +} from '../pages/walletSendPage'; Given(/^I have a wallet with funds$/, async function () { await this.waitUntilContainsText( @@ -201,28 +207,15 @@ When(/^I click on the unmangle button$/, async function () { }); When(/^I open the token selection dropdown$/, async function () { - await this.click({ locator: '.WalletSendForm_component .SimpleInput_input', method: 'css' }); + await this.click(selectAssetDropDown); }); When(/^I select token "([^"]*)"$/, async function (tokenName) { - const tokenRows = await this.getElementsBy({ locator: '.TokenOptionRow_item_name', method: 'css' }); - for (const row of tokenRows) { - const name = await row.getText(); - if (name === tokenName) { - await row.click(); - } - } + await this.click(getTokenLocator(tokenName)); }); When(/^I open the amount dropdown and select send all$/, async function () { - await this.driver.executeScript( - `const dropdownInput = document.querySelector('input[value="Custom Amount"]').click; - const tokenList = document.querySelectorAll('.TokenOptionRow_item_name'); - for(let token of tokenList){ - if(token.innerHTML.startsWith('Send all')){ - token.click() - } - } -` - ); + await this.click(selectSendingAmountDropDown); + await this.driver.sleep(500); + await this.click(sendAllItem); }); diff --git a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js index 7b0b0545d3..4998eccc39 100644 --- a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js +++ b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js @@ -245,8 +245,8 @@ Then(/^I wait for (\d+) minute\(s\) the last transaction is confirmed$/, async f const topTxState = await getTxStatus(topTx); if(txSuccessfulStatuses.includes(topTxState.toLowerCase())){ const endTime = Date.now(); - this.webDriverLogger.debug(`Step: The new transaction is confirmed`); - this.webDriverLogger.debug(`Step: Waiting time is ${(endTime - startTime) / 1000} seconds`); + this.webDriverLogger.info(`Step: The new transaction is confirmed`); + this.webDriverLogger.info(`Step: Waiting time is ${(endTime - startTime) / 1000} seconds`); return; } await this.driver.sleep(1000); From 21018623993c6dd274724289897e015159e5210c Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 19 Aug 2022 16:59:01 +0300 Subject: [PATCH 039/199] Added tests: Sending a custom token Sending a registered token Sending all --- .../yoroi-extension/features/smoke.feature | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/smoke.feature b/packages/yoroi-extension/features/smoke.feature index 7ba7dcf9a9..e44e3d143e 100644 --- a/packages/yoroi-extension/features/smoke.feature +++ b/packages/yoroi-extension/features/smoke.feature @@ -47,4 +47,71 @@ Feature: Smoke tests And I click the transaction page button Then I should see the summary screen And I should see 1 pending transactions - Then I wait for 3 minute(s) the last transaction is confirmed \ No newline at end of file + Then I wait for 3 minute(s) the last transaction is confirmed + + @smoke-004 + Scenario: Sending intrawallet transaction. Custom token (smoke-004) + Given There is a Shelley wallet stored named Second-Smoke-Test-Wallet + And I have a wallet with funds + Then I go to the send transaction screen + And I open the token selection dropdown + And I select token "yoroi" + And I fill the form: + | address | amount | + | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | 5 | + And I click on the next button in the wallet send form + And I see send money confirmation dialog + And I enter the wallet password: + | password | + | asdfasdfasdf | + And I submit the wallet send form + Then I should see the successfully sent page + And I click the transaction page button + Then I should see the summary screen + And I should see 1 pending transactions + Then I wait for 3 minute(s) the last transaction is confirmed + + @smoke-005 + Scenario: Sending intrawallet transaction. Registered token (smoke-005) + Given There is a Shelley wallet stored named Second-Smoke-Test-Wallet + And I have a wallet with funds + Then I go to the send transaction screen + And I open the token selection dropdown + And I select token "SPACE" + And I fill the form: + | address | amount | + | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | 55 | + And I click on the next button in the wallet send form + And I see send money confirmation dialog + And I enter the wallet password: + | password | + | asdfasdfasdf | + And I submit the wallet send form + Then I should see the successfully sent page + And I click the transaction page button + Then I should see the summary screen + And I should see 1 pending transactions + Then I wait for 3 minute(s) the last transaction is confirmed + + @smoke-006 + Scenario: Sending intrawallet transaction. Send all (smoke-006) + Given There is a Shelley wallet stored named Second-Smoke-Test-Wallet + And I have a wallet with funds + Then I go to the send transaction screen + And I open the token selection dropdown + And I select token "ADA" + And I fill the address of the form: + | address | + | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | + And I open the amount dropdown and select send all + And I click on the next button in the wallet send form + And I see send money confirmation dialog + And I enter the wallet password: + | password | + | asdfasdfasdf | + And I submit the wallet send form + Then I should see the successfully sent page + And I click the transaction page button + Then I should see the summary screen + And I should see 1 pending transactions + Then I wait for 3 minute(s) the last transaction is confirmed From fee75bba5a331cb2581c27d4a1360debbb122a86 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Aug 2022 13:16:32 +0300 Subject: [PATCH 040/199] Added a new id for tests --- .../app/containers/wallet/staking/CardanoStakingPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/containers/wallet/staking/CardanoStakingPage.js b/packages/yoroi-extension/app/containers/wallet/staking/CardanoStakingPage.js index 799f27f896..81d23d7670 100644 --- a/packages/yoroi-extension/app/containers/wallet/staking/CardanoStakingPage.js +++ b/packages/yoroi-extension/app/containers/wallet/staking/CardanoStakingPage.js @@ -114,7 +114,7 @@ class CardanoStakingPage extends Component { const isWalletWithNoFunds = balance != null && balance.getDefaultEntry().amount.isZero(); const classicCardanoStakingPage = ( -
    +
    {this.getDialog()} Date: Sun, 21 Aug 2022 13:17:22 +0300 Subject: [PATCH 041/199] Added the page object for the mainnet stake pool page --- .../features/pages/walletDelegationPage.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/yoroi-extension/features/pages/walletDelegationPage.js diff --git a/packages/yoroi-extension/features/pages/walletDelegationPage.js b/packages/yoroi-extension/features/pages/walletDelegationPage.js new file mode 100644 index 0000000000..e7158bea29 --- /dev/null +++ b/packages/yoroi-extension/features/pages/walletDelegationPage.js @@ -0,0 +1,17 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const iframe: LocatorObject = { + locator: '#classicCardanoStakingPage > iframe', + method: 'css', +}; +export const iframePoolIdInput: LocatorObject = { locator: '//div/form/input', method: 'xpath' }; +export const iframePoolIdSearchButton: LocatorObject = { + locator: '//div/form/button', + method: 'xpath', +}; +export const iframeFirstPoolDelegateButton: LocatorObject = { + locator: '//table/tbody/tr/td/button', + method: 'xpath', +}; From 88c624e7e2be18afea1f4927f286b44433a406b9 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Aug 2022 13:17:43 +0300 Subject: [PATCH 042/199] Updated wallet-delegation-steps.js --- .../wallet-delegation-steps.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js index b2d43721f7..7c21132cbb 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js @@ -1,11 +1,34 @@ // @flow import { Given, When, Then } from 'cucumber'; +import { + iframe, + iframeFirstPoolDelegateButton, + iframePoolIdInput, + iframePoolIdSearchButton +} from '../pages/walletDelegationPage'; When(/^I go to the delegation by id screen$/, async function () { await this.click({ locator: '.cardanoStake', method: 'css' }); }); +When(/^I go to the delegation list screen$/, async function () { + await this.click({ locator: '.stakeSimulator', method: 'css' }); + await this.waitForElement({ locator: 'classicCardanoStakingPage', method: 'id' }); +}); + +Then(/^I select the pool with the id "([^"]*)"$/, async function(stakePoolId) { + const iframeElement = await this.findElement(iframe); + await this.driver.switchTo().frame(iframeElement); + await this.waitForElement(iframePoolIdInput); + await this.waitForElement(iframeFirstPoolDelegateButton); + await this.input(iframePoolIdInput, stakePoolId); + await this.click(iframePoolIdSearchButton); + await this.driver.sleep(1000); + await this.click(iframeFirstPoolDelegateButton); + await this.driver.switchTo().defaultContent(); +}); + When(/^I fill the delegation id form:$/, async function (table) { const fields = table.hashes()[0]; await this.input({ locator: "input[name='poolId']", method: 'css' }, fields.stakePoolId); From cf4baff649b73b8e3da81c2c2c342a2af3c5c1e3 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Aug 2022 13:18:21 +0300 Subject: [PATCH 043/199] Added tests: Delegation Deregister --- .../yoroi-extension/features/smoke.feature | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/yoroi-extension/features/smoke.feature b/packages/yoroi-extension/features/smoke.feature index e44e3d143e..86e035df1d 100644 --- a/packages/yoroi-extension/features/smoke.feature +++ b/packages/yoroi-extension/features/smoke.feature @@ -115,3 +115,37 @@ Feature: Smoke tests Then I should see the summary screen And I should see 1 pending transactions Then I wait for 3 minute(s) the last transaction is confirmed + + @smoke-007 + Scenario: Delegating (smoke-007) + Given There is a Shelley wallet stored named Second-Smoke-Test-Wallet + And I have a wallet with funds + Given I go to the delegation list screen + Then I select the pool with the id "df1750df9b2df285fcfb50f4740657a18ee3af42727d410c37b86207" + Then I see the delegation confirmation dialog + And I enter the wallet password: + | password | + | asdfasdfasdf | + Then I submit the wallet send form + Given I click on see dashboard + Then I should see the dashboard screen + Then I go to the transaction history screen + And I should see 1 pending transactions + Then I wait for 3 minute(s) the last transaction is confirmed + + @smoke-008 + Scenario: Deregister (smoke-008) + Given There is a Shelley wallet stored named Second-Smoke-Test-Wallet + And I have a wallet with funds + When I click on the withdraw button + Then I click on the checkbox + And I click the next button + And I see the deregistration for the transaction + And I enter the wallet password: + | password | + | asdfasdfasdf | + When I confirm Yoroi transfer funds + Then I do not see the deregistration for the transaction + Then I should see the summary screen + And I should see 1 pending transactions + Then I wait for 3 minute(s) the last transaction is confirmed \ No newline at end of file From b9cceb9422e90ae5d05b1236c275e934d6ff473e Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Aug 2022 14:25:24 +0300 Subject: [PATCH 044/199] Added scripts to run smoke tests --- packages/yoroi-extension/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index 2137d2cd81..c363af2aa3 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -41,6 +41,8 @@ "test:run:e2e:dApp:firefox": "npm run test:run:base -- --world-parameters '{\"browser\":\"firefox\"}' features/*.feature --tags '@dApp'", "test:run:e2e:trezor:chrome": "npm run test:run:base -- --world-parameters '{\"browser\":\"chrome\"}' features/*.feature --tags '@Trezor'", "test:run:e2e:trezor:firefox": "npm run test:run:base -- --world-parameters '{\"browser\":\"firefox\"}' features/*.feature --tags '@Trezor'", + "test:run:e2e:smoke:chrome": "npm run test:run:base -- --world-parameters '{\"browser\":\"chrome\"}' features/*.feature --tags '@smoke'", + "test:run:e2e:smoke:firefox": "npm run test:run:base -- --world-parameters '{\"browser\":\"firefox\"}' features/*.feature --tags '@smoke'", "test:run:tag:chrome": "npm run test:run:base -- --world-parameters '{\"browser\":\"chrome\"}' features/*.feature --tags", "test:run:tag:brave": "npm run test:run:base -- --world-parameters '{\"browser\":\"brave\"}' features/*.feature --tags", "test:run:tag:firefox": "npm run test:run:base -- --world-parameters '{\"browser\":\"firefox\"}' features/*.feature --tags", From 1c75b745c151e61f54966037c2e8b990a3e3772a Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Aug 2022 15:05:13 +0300 Subject: [PATCH 045/199] Updated smoke tests --- packages/yoroi-extension/features/smoke.feature | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/features/smoke.feature b/packages/yoroi-extension/features/smoke.feature index 9bef5e1016..88cc98272b 100644 --- a/packages/yoroi-extension/features/smoke.feature +++ b/packages/yoroi-extension/features/smoke.feature @@ -72,15 +72,15 @@ Feature: Smoke tests Then I wait for 3 minute(s) the last transaction is confirmed @smoke-005 - Scenario: Sending intrawallet transaction. Registered token (smoke-005) + Scenario Outline: Sending intrawallet transaction. Registered token - (smoke-005) Given There is a Shelley wallet stored named Second-Smoke-Test-Wallet And I have a wallet with funds Then I go to the send transaction screen And I open the token selection dropdown - And I select token "SPACE" + And I select token "" And I fill the form: | address | amount | - | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | 55 | + | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | 15 | And I click on the next button in the wallet send form And I see send money confirmation dialog And I enter the wallet password: @@ -93,6 +93,12 @@ Feature: Smoke tests And I should see 1 pending transactions Then I wait for 3 minute(s) the last transaction is confirmed + Examples: + | token | + | SPACE | + | DRIP | + | XRAY | + @smoke-006 Scenario: Sending intrawallet transaction. Send all (smoke-006) Given There is a Shelley wallet stored named Second-Smoke-Test-Wallet From 84054d697a315644ff62b843df96b87d9a07b0de Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Aug 2022 15:24:46 +0300 Subject: [PATCH 046/199] added the smoke CI --- .github/workflows/tests.yml | 92 +++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 028a1d0fe9..92156ecf68 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -235,6 +235,98 @@ jobs: working-directory: ./packages/yoroi-extension run: xvfb-run -a -e /dev/stdout -s "-screen 0 1920x1080x24" npm run test:run:e2e:trezor:${{ matrix.browser }} + - name: Archive tests screenshots and logs + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + with: + name: testRunsData + path: ./packages/yoroi-extension/testRunsData + + E2E_smoke_tests: + if: github.event.review && (contains(github.event.review.body, '/release-check')) + runs-on: ubuntu-22.04 + strategy: + matrix: + browser: [ 'chrome', 'firefox' ] + fail-fast: false + steps: + - name: Forcefully update the Chrome browser + if: matrix.browser=='chrome' + run: | + wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + sudo sh -c 'echo "deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' + sudo apt-get update + sudo apt-get --only-upgrade install google-chrome-stable + + - name: Install Firefox Developer Edition + if: matrix.browser=='firefox' + run: | + wget -c "https://download.mozilla.org/?product=firefox-devedition-latest-ssl&os=linux64&lang=en-US" -O - | sudo tar -xj -C /opt + sudo rm -rf /opt/firefoxdev + sudo mv /opt/firefox /opt/firefoxdev + echo "FIREFOX_DEV=/opt/firefoxdev/firefox-bin" >> $GITHUB_ENV + + - name: Read .nvmrc + run: echo ::set-output name=NVMRC::$(cat .nvmrc) + id: nvm + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: '${{ steps.nvm.outputs.NVMRC }}' + + - name: Cache extension node modules + # https://docs.github.com/en/actions/guides/caching-dependencies-to-speed-up-workflows + uses: actions/cache@v2 + env: + cache-name: cache-yoroi-extension-node-modules + with: + # https://github.com/actions/cache/blob/main/examples.md#node---npm + # It is recommended to cache the NPM cache (~/.npm) instead of node_modules. + # But we put node version into the cache key and cache node_modules. + path: packages/yoroi-extension/node_modules + key: ${{ runner.os }}-build-${{ env.cache-name }}-node-${{ steps.nvm.outputs.NVMRC }}-${{ hashFiles('packages/yoroi-extension/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Cache connector node modules + # https://docs.github.com/en/actions/guides/caching-dependencies-to-speed-up-workflows + uses: actions/cache@v2 + env: + cache-name: cache-yoroi-connector-node-modules + with: + # https://github.com/actions/cache/blob/main/examples.md#node---npm + # It is recommended to cache the NPM cache (~/.npm) instead of node_modules. + # But we put node version into the cache key and cache node_modules. + path: packages/yoroi-ergo-connector/node_modules + key: ${{ runner.os }}-build-${{ env.cache-name }}-node-${{ steps.nvm.outputs.NVMRC }}-${{ hashFiles('packages/yoroi-ergo-connector/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: npm install + run: | + . install-all.sh + + - name: Build the test mainnet version + working-directory: ./packages/yoroi-extension + run: npm run test:build:mainnet + + - name: Create the report's folder + working-directory: ./packages/yoroi-extension + run: | + mkdir reports + touch ./reports/cucumberReports.json + + - name: Run smoke tests + working-directory: ./packages/yoroi-extension + env: + FIRST_SMOKE_TEST_WALLET: ${{ secrets.FIRST_SMOKE_TEST_WALLET }} + SECOND_SMOKE_TEST_WALLET: ${{ secrets.SECOND_SMOKE_TEST_WALLET }} + run: npm run test:run:e2e:smoke:${{ matrix.browser }} - name: Archive tests screenshots and logs if: ${{ failure() }} uses: actions/upload-artifact@v3 From 27a4627f7f26fabeb931a176cddaf8e9bcf18494 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Aug 2022 15:30:08 +0300 Subject: [PATCH 047/199] fixed CI --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 92156ecf68..7f1e3a577b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -250,6 +250,7 @@ jobs: browser: [ 'chrome', 'firefox' ] fail-fast: false steps: + - uses: actions/checkout@v2 - name: Forcefully update the Chrome browser if: matrix.browser=='chrome' run: | From ead9e8e4f7ce4f69a23ec362524281020de16312 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Aug 2022 15:55:26 +0300 Subject: [PATCH 048/199] fixed CI --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7f1e3a577b..afb1d679e4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -327,7 +327,8 @@ jobs: env: FIRST_SMOKE_TEST_WALLET: ${{ secrets.FIRST_SMOKE_TEST_WALLET }} SECOND_SMOKE_TEST_WALLET: ${{ secrets.SECOND_SMOKE_TEST_WALLET }} - run: npm run test:run:e2e:smoke:${{ matrix.browser }} + run: xvfb-run -a -e /dev/stdout -s "-screen 0 1920x1080x24" npm run test:run:e2e:smoke:${{ matrix.browser }} + - name: Archive tests screenshots and logs if: ${{ failure() }} uses: actions/upload-artifact@v3 From b2e0e9102bc4673c7722e1f4acbef341e7779589 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Aug 2022 19:15:13 +0300 Subject: [PATCH 049/199] improved search of the "delegate" button --- .../wallet-delegation-steps.js | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js index 7c21132cbb..bbe002f154 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js @@ -3,7 +3,6 @@ import { Given, When, Then } from 'cucumber'; import { iframe, - iframeFirstPoolDelegateButton, iframePoolIdInput, iframePoolIdSearchButton } from '../pages/walletDelegationPage'; @@ -18,15 +17,34 @@ When(/^I go to the delegation list screen$/, async function () { }); Then(/^I select the pool with the id "([^"]*)"$/, async function(stakePoolId) { + await this.webDriverLogger.info(`Step: "I select the pool with the id ${stakePoolId}" has started`); const iframeElement = await this.findElement(iframe); await this.driver.switchTo().frame(iframeElement); + await this.webDriverLogger.info(`Step: Switched to stake pool iframe`); await this.waitForElement(iframePoolIdInput); - await this.waitForElement(iframeFirstPoolDelegateButton); + await this.driver.wait(async () => { + const allButtons = await this.findElements({ locator: '//button', method: 'xpath' }); + for (let i = 0; i < allButtons.length; i++) { + const buttonText = await allButtons[i].getText(); + if (buttonText.toLowerCase() === 'delegate'){ + return true; + } + } + return false; + }); await this.input(iframePoolIdInput, stakePoolId); await this.click(iframePoolIdSearchButton); await this.driver.sleep(1000); - await this.click(iframeFirstPoolDelegateButton); + const allButtons = await this.findElements({ locator: '//button', method: 'xpath' }); + for (let i = 0; i < allButtons.length; i++) { + const buttonText = await allButtons[i].getText(); + if (buttonText.toLowerCase() === 'delegate'){ + await allButtons[i].click(); + break; + } + } await this.driver.switchTo().defaultContent(); + await this.webDriverLogger.info(`Step: Switched back to the default content`); }); When(/^I fill the delegation id form:$/, async function (table) { From 6e6dda6550ea06909e29cf0789313ad2ebdb8f9f Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Aug 2022 23:09:36 +0300 Subject: [PATCH 050/199] Only chrome, for now --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index afb1d679e4..ebbb615d50 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -122,7 +122,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - browser: [ 'chrome', 'firefox' ] + browser: [ 'chrome' ] fail-fast: false steps: - name: Forcefully update the Chrome browser @@ -247,7 +247,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - browser: [ 'chrome', 'firefox' ] + browser: [ 'chrome' ] fail-fast: false steps: - uses: actions/checkout@v2 From 369d75eb38a5b7dced62f622c82c3a90259a5b9f Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 21 Aug 2022 23:10:38 +0300 Subject: [PATCH 051/199] Only chrome 2, for now --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ebbb615d50..7da040a90c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,7 @@ jobs: runs-on: macos-12 strategy: matrix: - browser: ['chrome', 'firefox'] + browser: ['chrome'] fail-fast: false steps: - uses: actions/checkout@v2 @@ -122,7 +122,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - browser: [ 'chrome' ] + browser: ['chrome'] fail-fast: false steps: - name: Forcefully update the Chrome browser @@ -247,7 +247,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - browser: [ 'chrome' ] + browser: ['chrome'] fail-fast: false steps: - uses: actions/checkout@v2 From 8579e25fb99552db483b95d2ce9115860f7e7754 Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Wed, 24 Aug 2022 17:44:27 -0300 Subject: [PATCH 052/199] Changed structure for logs --- .github/workflows/tests.yml | 18 +++++++++--------- packages/yoroi-extension/.gitignore | 1 + .../features/mock-chain/mockCardanoServer.js | 2 +- .../features/step_definitions/common-steps.js | 2 +- .../support/helpers/common-constants.js | 4 ++-- .../features/support/webdriver.js | 9 +++++++-- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7da040a90c..e999dd7374 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,7 @@ jobs: runs-on: macos-12 strategy: matrix: - browser: ['chrome'] + browser: ['chrome', 'firefox'] fail-fast: false steps: - uses: actions/checkout@v2 @@ -114,15 +114,15 @@ jobs: if: ${{ failure() }} uses: actions/upload-artifact@v3 with: - name: testRunsData - path: ./packages/yoroi-extension/testRunsData + name: testRunsData_${{ matrix.browser }} + path: ./packages/yoroi-extension/testRunsData_${{ matrix.browser }} Trezor_Model_T_emulator: if: github.event.review && (github.event.review.state == 'approved' || contains(github.event.review.body, '/check') || contains(github.event.review.body, '/trezor-check')) runs-on: ubuntu-22.04 strategy: matrix: - browser: ['chrome'] + browser: ['chrome', 'firefox'] fail-fast: false steps: - name: Forcefully update the Chrome browser @@ -239,15 +239,15 @@ jobs: if: ${{ failure() }} uses: actions/upload-artifact@v3 with: - name: testRunsData - path: ./packages/yoroi-extension/testRunsData + name: testRunsData_${{ matrix.browser }} + path: ./packages/yoroi-extension/testRunsData_${{ matrix.browser }} E2E_smoke_tests: if: github.event.review && (contains(github.event.review.body, '/release-check')) runs-on: ubuntu-22.04 strategy: matrix: - browser: ['chrome'] + browser: ['chrome', 'firefox'] fail-fast: false steps: - uses: actions/checkout@v2 @@ -333,5 +333,5 @@ jobs: if: ${{ failure() }} uses: actions/upload-artifact@v3 with: - name: testRunsData - path: ./packages/yoroi-extension/testRunsData \ No newline at end of file + name: testRunsData_${{ matrix.browser }} + path: ./packages/yoroi-extension/testRunsData_${{ matrix.browser }} \ No newline at end of file diff --git a/packages/yoroi-extension/.gitignore b/packages/yoroi-extension/.gitignore index 4977e643dc..e5f88b3bce 100644 --- a/packages/yoroi-extension/.gitignore +++ b/packages/yoroi-extension/.gitignore @@ -8,6 +8,7 @@ production-key.pem build/ dev/ testRunsData/ +testRunsData* storybook-static/ __screenshots__/ screenshots/ diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js index 7ae28156e0..88d0c7fbcb 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js @@ -92,7 +92,7 @@ export function getMockServer(settings: { outputLog?: boolean, ... }): typeof MockServer { - const dir = `${testRunsDataDir}cardanoMockServerLogs`; + const dir = `${testRunsDataDir}_cardanoMockServerLogs`; fs.mkdirSync(dir); const loggerPath = `${dir}/cardanoMockServerLog_${getLogDate()}.log`; diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 3a0d068ba0..2616ce35d2 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -244,7 +244,7 @@ setDefinitionFunctionWrapper((fn, _, pattern) => { async function createDirInTestRunsData(driver, subdirectoryName) { const cap = await driver.getCapabilities(); const browserName = cap.getBrowserName(); - const dir = `${testRunsDataDir}/${browserName}/${testProgress.scenarioName}/${subdirectoryName}`; + const dir = `${testRunsDataDir}_${browserName}/${testProgress.scenarioName}/${subdirectoryName}`; if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } diff --git a/packages/yoroi-extension/features/support/helpers/common-constants.js b/packages/yoroi-extension/features/support/helpers/common-constants.js index 5af6b4a79d..7c35d81d2b 100644 --- a/packages/yoroi-extension/features/support/helpers/common-constants.js +++ b/packages/yoroi-extension/features/support/helpers/common-constants.js @@ -1,8 +1,8 @@ // @flow -export const testRunsDataDir = './testRunsData/'; +export const testRunsDataDir = './testRunsData'; export const snapshotsDir = './features/yoroi_snapshots/'; -export const testRunsLogsDir = `${testRunsDataDir}Logs/`; +export const testRunsLogsDir = `${testRunsDataDir}/Logs/`; export const mockDAppLogsDir = `${testRunsLogsDir}mockDApp/`; export const windowManagerLogsDir = `${testRunsLogsDir}windowManager/`; diff --git a/packages/yoroi-extension/features/support/webdriver.js b/packages/yoroi-extension/features/support/webdriver.js index be50d8568a..a3013f8523 100644 --- a/packages/yoroi-extension/features/support/webdriver.js +++ b/packages/yoroi-extension/features/support/webdriver.js @@ -136,7 +136,7 @@ function CustomWorld(cmdInput: WorldInput) { this._allLoggers = []; - const logsDir = `${testRunsDataDir}${this.getBrowser()}/Logs/` + const logsDir = `${testRunsDataDir}_${this.getBrowser()}/` const mockAndWMLogDir = `${logsDir}mockAndWMLogs`; if (!fs.existsSync(mockAndWMLogDir)) { @@ -157,8 +157,13 @@ function CustomWorld(cmdInput: WorldInput) { this.webDriverLogger = simpleNodeLogger.createSimpleFileLogger(webDriverLogPath); this._allLoggers.push(this.webDriverLogger); - const trezorEmuLogPath = `${logsDir}trezorEmulatorController_${getLogDate()}.log`; + const trezorEmulatorLogsDir = `${logsDir}trezorEmulatorLogs`; + if (!fs.existsSync(trezorEmulatorLogsDir)) { + fs.mkdirSync(trezorEmulatorLogsDir, { recursive: true }); + } + const trezorEmuLogPath = `${trezorEmulatorLogsDir}/trezorEmulatorController_${getLogDate()}.log`; this.trezorEmuLogger = simpleNodeLogger.createSimpleFileLogger(trezorEmuLogPath); + this._allLoggers.push(this.trezorEmuLogger); this.trezorController = undefined; this.sendToAllLoggers = (message: string, level: string = 'info') => { From f83767aaf7347f34099ca811e16f3a90f9314270 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 25 Aug 2022 14:47:55 +0300 Subject: [PATCH 053/199] flow fixes --- .../features/mock-chain/TestWallets.js | 10 +++++++--- .../features/step_definitions/common-steps.js | 16 +++++----------- .../step_definitions/transactions-steps.js | 2 +- .../step_definitions/wallet-paper-steps.js | 2 +- .../step_definitions/wallet-restoration-steps.js | 2 +- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/yoroi-extension/features/mock-chain/TestWallets.js b/packages/yoroi-extension/features/mock-chain/TestWallets.js index f03584978e..8199f340f3 100644 --- a/packages/yoroi-extension/features/mock-chain/TestWallets.js +++ b/packages/yoroi-extension/features/mock-chain/TestWallets.js @@ -10,6 +10,10 @@ export type RestorationInput = {| deviceId?: ?string, |}; +function getMnemonicFromEnv(walletName): string { + return process.env[walletName] ?? ''; +} + function createWallet(payload: {| name: string, mnemonic: string, @@ -30,7 +34,7 @@ function createWallet(payload: {| // You can use this website to generate more mnemonics if you need for testing // https://iancoleman.io/bip39/ -type WalletNames = +export type WalletNames = 'shelley-simple-24' | 'shelley-simple-15' | 'shelley-delegated' | @@ -167,12 +171,12 @@ export const testWallets: { [key: WalletNames]: RestorationInput, ... } = Object }), createWallet({ name: ('First-Smoke-Test-Wallet': WalletNames), - mnemonic: process.env.FIRST_SMOKE_TEST_WALLET, + mnemonic: getMnemonicFromEnv('FIRST_SMOKE_TEST_WALLET'), plate: 'XONT-4910' }), createWallet({ name: ('Second-Smoke-Test-Wallet': WalletNames), - mnemonic: process.env.SECOND_SMOKE_TEST_WALLET, + mnemonic: getMnemonicFromEnv('SECOND_SMOKE_TEST_WALLET'), plate: 'XZHD-1651', }), ); diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 3a0d068ba0..b66a1de61c 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -26,7 +26,7 @@ import { expect } from 'chai'; import { satisfies } from 'semver'; import { truncateLongName } from '../../app/utils/formatters'; import stableStringify from 'json-stable-stringify'; -import type { RestorationInput } from '../mock-chain/TestWallets'; +import type { RestorationInput, WalletNames } from '../mock-chain/TestWallets'; import { waitUntilUrlEquals, navigateTo } from '../support/helpers/route-helpers'; import { promises as fsAsync } from 'fs'; import { @@ -190,12 +190,6 @@ After(async function (scenario) { await helpers.sleep(500); }); -type WalletEra = {| - era: - | 'shelley' - | 'byron', -|} - export async function getPlates(customWorld: any): Promise { // check plate in confirmation dialog let plateElements = await customWorld.driver.findElements( @@ -291,8 +285,8 @@ async function getLogs(driver, name, loggingType) { async function restoreWallet ( customWorld: any, - walletEra: WalletEra, - walletName: string + walletEra: string, + walletName: WalletNames ): Promise { const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); @@ -366,12 +360,12 @@ Given(/^There is an Ergo wallet stored named ([^"]*)$/, async function (walletNa await checkWalletPlate(this, walletName, restoreInfo); }); -Given(/^There is a Shelley wallet stored named ([^"]*)$/, async function (walletName) { +Given(/^There is a Shelley wallet stored named ([^"]*)$/, async function (walletName: WalletNames) { this.webDriverLogger.info(`Step: There is a Shelley wallet stored named ${walletName}`); await restoreWallet(this, 'shelley', walletName); }); -Given(/^There is a Byron wallet stored named ([^"]*)$/, async function (walletName) { +Given(/^There is a Byron wallet stored named ([^"]*)$/, async function (walletName: WalletNames) { this.webDriverLogger.info(`Step: There is a Byron wallet stored named ${walletName}`); await restoreWallet(this, 'byron', walletName); }); diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index 746f835f16..5b8001ff18 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -28,7 +28,7 @@ Given(/^I have a wallet with funds$/, async function () { const balanceTextElement = await this.findElement({ locator: '.NavWalletDetails_amount', method: 'css' }); const balanceText = await balanceTextElement.getText(); const [balance, ] = balanceText.split(' '); - expect(parseFloat(balance, 10), 'The wallet is empty').to.be.above(0); + expect(parseFloat(balance), 'The wallet is empty').to.be.above(0); }); When(/^I go to the send transaction screen$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js index b5bbbd0dfe..c05f8fefc1 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js @@ -4,7 +4,7 @@ import { Given, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import { truncateAddress, } from '../../app/utils/formatters'; -import { enterRecoveryPhrase } from '../support/helpers/helpers'; +import { enterRecoveryPhrase } from '../pages/restoreWalletPage'; // ========== Paper wallet ========== diff --git a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js index 511ee8506b..35ac0d7ed1 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js @@ -5,9 +5,9 @@ import { By, error, Key } from 'selenium-webdriver'; import i18n from '../support/helpers/i18n-helpers'; import { expect } from 'chai'; import { checkErrorByTranslationId, getPlates } from './common-steps'; -import { enterRecoveryPhrase } from '../support/helpers/helpers'; import { cleanRecoverInput, + enterRecoveryPhrase, errorInvalidRecoveryPhrase, getWords, paperPasswordInput, From 7e4d055aed9458ac2aa9dfd350e96687c7bf9d3e Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Thu, 25 Aug 2022 10:49:37 -0300 Subject: [PATCH 054/199] Added changes to logs --- .../features/mock-chain/mockCardanoServer.js | 4 +- .../features/step_definitions/common-steps.js | 38 ++++++++++++++++--- .../support/helpers/common-constants.js | 3 -- .../features/support/webdriver.js | 34 +---------------- 4 files changed, 37 insertions(+), 42 deletions(-) diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js index 88d0c7fbcb..46dee28de5 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js @@ -93,7 +93,9 @@ export function getMockServer(settings: { ... }): typeof MockServer { const dir = `${testRunsDataDir}_cardanoMockServerLogs`; - fs.mkdirSync(dir); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } const loggerPath = `${dir}/cardanoMockServerLog_${getLogDate()}.log`; logger = simpleNodeLogger.createSimpleFileLogger(loggerPath); diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 2616ce35d2..9f489968a6 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -79,10 +79,13 @@ import { walletRecoveryPhraseDisplayDialog } from '../pages/createWalletPage'; import * as helpers from '../support/helpers/helpers'; +import { MockDAppWebpage } from '../mock-dApp-webpage'; +import { WindowManager } from '../support/windowManager'; + +const simpleNodeLogger = require('simple-node-logger'); const { promisify } = require('util'); const fs = require('fs'); -const rimraf = require('rimraf'); /** We need to keep track of our progress in testing to give unique names to screenshots */ const testProgress = { @@ -91,9 +94,8 @@ const testProgress = { step: 0, }; -BeforeAll(() => { - rimraf.sync(testRunsDataDir); - fs.mkdirSync(testRunsDataDir); +// eslint-disable-next-line prefer-arrow-callback +BeforeAll(function() { setDefaultTimeout(20 * 1000); CardanoServer.getMockServer({}); @@ -105,7 +107,8 @@ AfterAll(() => { ErgoServer.closeMockServer(); }); -Before(scenario => { +// eslint-disable-next-line prefer-arrow-callback +Before(function(scenario) { const pathItems = scenario.sourceLocation.uri.split('/'); // eslint-disable-next-line no-console console.log( @@ -117,6 +120,30 @@ Before(scenario => { testProgress.scenarioName = scenario.pickle.name.replace(/[^0-9a-z_ ]/gi, ''); testProgress.lineNum = scenario.sourceLocation.line; testProgress.step = 0; + + const logsDir = `${testRunsDataDir}_${this.getBrowser()}/${testProgress.scenarioName}/` + + if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir, { recursive: true }); + } + + const mockAndWMLogPath = `${logsDir}/mockAndWM.log`; + const mockAndWMLogger = simpleNodeLogger.createSimpleFileLogger(mockAndWMLogPath); + this.windowManager = new WindowManager(this.driver, mockAndWMLogger); + this.windowManager.init().then().catch(); + this._allLoggers.push(mockAndWMLogger); + this.mockDAppPage = new MockDAppWebpage(this.driver, mockAndWMLogger); + + const webDriverLogPath = `${logsDir}/webDriver.log`; + this.webDriverLogger = simpleNodeLogger.createSimpleFileLogger(webDriverLogPath); + this._allLoggers.push(this.webDriverLogger); + + const trezorEmuLogPath = `${logsDir}/trezorEmulatorController.log`; + this.trezorEmuLogger = simpleNodeLogger.createSimpleFileLogger(trezorEmuLogPath); + this._allLoggers.push(this.trezorEmuLogger); + + this.sendToAllLoggers(`### ${pathItems[pathItems.length - 2]}. The scenario "${scenario.pickle.name}" has started`); + }); Before({ tags: 'not @TestAssuranceChain' }, () => { @@ -244,6 +271,7 @@ setDefinitionFunctionWrapper((fn, _, pattern) => { async function createDirInTestRunsData(driver, subdirectoryName) { const cap = await driver.getCapabilities(); const browserName = cap.getBrowserName(); + const dir = `${testRunsDataDir}_${browserName}/${testProgress.scenarioName}/${subdirectoryName}`; if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); diff --git a/packages/yoroi-extension/features/support/helpers/common-constants.js b/packages/yoroi-extension/features/support/helpers/common-constants.js index 7c35d81d2b..116bea2331 100644 --- a/packages/yoroi-extension/features/support/helpers/common-constants.js +++ b/packages/yoroi-extension/features/support/helpers/common-constants.js @@ -2,9 +2,6 @@ export const testRunsDataDir = './testRunsData'; export const snapshotsDir = './features/yoroi_snapshots/'; -export const testRunsLogsDir = `${testRunsDataDir}/Logs/`; -export const mockDAppLogsDir = `${testRunsLogsDir}mockDApp/`; -export const windowManagerLogsDir = `${testRunsLogsDir}windowManager/`; export const mailsacAPIKey = process.env.MAILSAC_API_KEY; export const mailsacEmail = 'emurgoqa@mailsac.com'; diff --git a/packages/yoroi-extension/features/support/webdriver.js b/packages/yoroi-extension/features/support/webdriver.js index a3013f8523..6fe6e6aee8 100644 --- a/packages/yoroi-extension/features/support/webdriver.js +++ b/packages/yoroi-extension/features/support/webdriver.js @@ -6,15 +6,11 @@ import chrome from 'selenium-webdriver/chrome'; import firefox from 'selenium-webdriver/firefox'; import path from 'path'; import { RustModule } from '../../app/api/ada/lib/cardanoCrypto/rustLoader'; -import { getMethod, getLogDate } from './helpers/helpers'; -import { WindowManager } from './windowManager'; -import { MockDAppWebpage } from '../mock-dApp-webpage'; -import { testRunsDataDir } from './helpers/common-constants'; +import { getMethod } from './helpers/helpers'; import { WebDriverError } from 'selenium-webdriver/lib/error'; import * as helpers from './helpers/helpers'; const fs = require('fs'); -const simpleNodeLogger = require('simple-node-logger'); function encode(file) { return fs.readFileSync(file, { encoding: 'base64' }); @@ -136,34 +132,6 @@ function CustomWorld(cmdInput: WorldInput) { this._allLoggers = []; - const logsDir = `${testRunsDataDir}_${this.getBrowser()}/` - - const mockAndWMLogDir = `${logsDir}mockAndWMLogs`; - if (!fs.existsSync(mockAndWMLogDir)) { - fs.mkdirSync(mockAndWMLogDir, { recursive: true }); - } - const mockAndWMLogPath = `${mockAndWMLogDir}/mockAndWMLog_${getLogDate()}.log`; - const mockAndWMLogger = simpleNodeLogger.createSimpleFileLogger(mockAndWMLogPath); - this.windowManager = new WindowManager(this.driver, mockAndWMLogger); - this.windowManager.init().then().catch(); - this._allLoggers.push(mockAndWMLogger); - this.mockDAppPage = new MockDAppWebpage(this.driver, mockAndWMLogger); - - const webDriverLogDir = `${logsDir}webDriverLogs`; - if (!fs.existsSync(webDriverLogDir)) { - fs.mkdirSync(webDriverLogDir, { recursive: true }); - } - const webDriverLogPath = `${webDriverLogDir}/webDriverLog_${getLogDate()}.log`; - this.webDriverLogger = simpleNodeLogger.createSimpleFileLogger(webDriverLogPath); - this._allLoggers.push(this.webDriverLogger); - - const trezorEmulatorLogsDir = `${logsDir}trezorEmulatorLogs`; - if (!fs.existsSync(trezorEmulatorLogsDir)) { - fs.mkdirSync(trezorEmulatorLogsDir, { recursive: true }); - } - const trezorEmuLogPath = `${trezorEmulatorLogsDir}/trezorEmulatorController_${getLogDate()}.log`; - this.trezorEmuLogger = simpleNodeLogger.createSimpleFileLogger(trezorEmuLogPath); - this._allLoggers.push(this.trezorEmuLogger); this.trezorController = undefined; this.sendToAllLoggers = (message: string, level: string = 'info') => { From b02d14a4896ee93abc6e7a6cf0c66aab55509114 Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Thu, 25 Aug 2022 10:52:24 -0300 Subject: [PATCH 055/199] Changing back BeforeAll to arrow callback --- .../yoroi-extension/features/step_definitions/common-steps.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 9f489968a6..a964096242 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -94,8 +94,7 @@ const testProgress = { step: 0, }; -// eslint-disable-next-line prefer-arrow-callback -BeforeAll(function() { +BeforeAll(() => { setDefaultTimeout(20 * 1000); CardanoServer.getMockServer({}); From 515440d326d89e7f22ae007ed8e7aef763e98d1b Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Thu, 25 Aug 2022 11:00:09 -0300 Subject: [PATCH 056/199] Changing double slash --- .../features/step_definitions/common-steps.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index a964096242..95ba30d1a4 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -126,18 +126,18 @@ Before(function(scenario) { fs.mkdirSync(logsDir, { recursive: true }); } - const mockAndWMLogPath = `${logsDir}/mockAndWM.log`; + const mockAndWMLogPath = `${logsDir}mockAndWM.log`; const mockAndWMLogger = simpleNodeLogger.createSimpleFileLogger(mockAndWMLogPath); this.windowManager = new WindowManager(this.driver, mockAndWMLogger); this.windowManager.init().then().catch(); this._allLoggers.push(mockAndWMLogger); this.mockDAppPage = new MockDAppWebpage(this.driver, mockAndWMLogger); - const webDriverLogPath = `${logsDir}/webDriver.log`; + const webDriverLogPath = `${logsDir}webDriver.log`; this.webDriverLogger = simpleNodeLogger.createSimpleFileLogger(webDriverLogPath); this._allLoggers.push(this.webDriverLogger); - const trezorEmuLogPath = `${logsDir}/trezorEmulatorController.log`; + const trezorEmuLogPath = `${logsDir}trezorEmulatorController.log`; this.trezorEmuLogger = simpleNodeLogger.createSimpleFileLogger(trezorEmuLogPath); this._allLoggers.push(this.trezorEmuLogger); From afa427a9a3ff9d19cf3e2453a8d1c91fd1c7e9ee Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Thu, 25 Aug 2022 11:18:55 -0300 Subject: [PATCH 057/199] Added changes to add to Loggers --- .../features/step_definitions/common-steps.js | 6 +++--- packages/yoroi-extension/features/support/webdriver.js | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 95ba30d1a4..78cebbbfc9 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -130,16 +130,16 @@ Before(function(scenario) { const mockAndWMLogger = simpleNodeLogger.createSimpleFileLogger(mockAndWMLogPath); this.windowManager = new WindowManager(this.driver, mockAndWMLogger); this.windowManager.init().then().catch(); - this._allLoggers.push(mockAndWMLogger); + this.addToLoggers(mockAndWMLogger); this.mockDAppPage = new MockDAppWebpage(this.driver, mockAndWMLogger); const webDriverLogPath = `${logsDir}webDriver.log`; this.webDriverLogger = simpleNodeLogger.createSimpleFileLogger(webDriverLogPath); - this._allLoggers.push(this.webDriverLogger); + this.addToLoggers(this.webDriverLogger); const trezorEmuLogPath = `${logsDir}trezorEmulatorController.log`; this.trezorEmuLogger = simpleNodeLogger.createSimpleFileLogger(trezorEmuLogPath); - this._allLoggers.push(this.trezorEmuLogger); + this.addToLoggers(this.trezorEmuLogger); this.sendToAllLoggers(`### ${pathItems[pathItems.length - 2]}. The scenario "${scenario.pickle.name}" has started`); diff --git a/packages/yoroi-extension/features/support/webdriver.js b/packages/yoroi-extension/features/support/webdriver.js index 6fe6e6aee8..c5b3486ace 100644 --- a/packages/yoroi-extension/features/support/webdriver.js +++ b/packages/yoroi-extension/features/support/webdriver.js @@ -134,6 +134,10 @@ function CustomWorld(cmdInput: WorldInput) { this.trezorController = undefined; + this.addToLoggers = (logger: any) => { + this._allLoggers.push(logger); + } ; + this.sendToAllLoggers = (message: string, level: string = 'info') => { for (const someLogger of this._allLoggers) { someLogger[level](message); From 26c711b621f9c63d6b55a8729c0fb09ed0658a1d Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 25 Aug 2022 20:21:48 +0300 Subject: [PATCH 058/199] flow fixes --- packages/yoroi-extension/features/support/webdriver.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/yoroi-extension/features/support/webdriver.js b/packages/yoroi-extension/features/support/webdriver.js index c5b3486ace..0a10e6fb2b 100644 --- a/packages/yoroi-extension/features/support/webdriver.js +++ b/packages/yoroi-extension/features/support/webdriver.js @@ -179,7 +179,7 @@ function CustomWorld(cmdInput: WorldInput) { this.getText = (locator: LocatorObject) => this.getElementBy(locator).getText(); - this.getValue = this.driver.getValue = async (locator: LocatorObject) => + this.getValue = async (locator: LocatorObject) => this.getElementBy(locator).getAttribute('value'); this.waitForElementLocated = async (locator: LocatorObject) => { @@ -189,7 +189,7 @@ function CustomWorld(cmdInput: WorldInput) { }; // Returns a promise that resolves to the element - this.waitForElement = this.driver.waitForElement = async (locator: LocatorObject) => { + this.waitForElement = async (locator: LocatorObject) => { this.webDriverLogger.info(`Webdriver: Waiting for element "${JSON.stringify(locator)}" to be visible`); await this.waitForElementLocated(locator); const element = await this.getElementBy(locator); @@ -206,9 +206,7 @@ function CustomWorld(cmdInput: WorldInput) { return element; }; - this.waitForElementNotPresent = this.driver.waitForElementNotPresent = async ( - locator: LocatorObject - ) => { + this.waitForElementNotPresent = async (locator: LocatorObject) => { this.webDriverLogger.info(`Webdriver: Waiting for element "${JSON.stringify(locator)}" not present`); await this.driver.wait(async () => { const elements = await this.getElementsBy(locator); From 8f4dd082581c8fcc3da6ee1c7d0a6df4cd197164 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 25 Aug 2022 20:51:36 +0300 Subject: [PATCH 059/199] minor changes --- .../yoroi-extension/features/step_definitions/common-steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index e019d664b7..fd9e559835 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -141,7 +141,7 @@ Before(function(scenario) { this.trezorEmuLogger = simpleNodeLogger.createSimpleFileLogger(trezorEmuLogPath); this.addToLoggers(this.trezorEmuLogger); - this.sendToAllLoggers(`### ${pathItems[pathItems.length - 2]}. The scenario "${scenario.pickle.name}" has started`); + this.sendToAllLoggers(`#### The scenario "${scenario.pickle.name}" has started ####`); }); From f311a177ed0fd10e275b13390e73e5a90ae0f913 Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Fri, 26 Aug 2022 12:58:12 -0300 Subject: [PATCH 060/199] Added first batch of locators to page objects --- .../features/pages/basicSetupPage.js | 13 ++ .../features/pages/daedalusTransferPage.js | 8 ++ .../features/pages/mainWindowPage.js | 5 + .../features/pages/newWalletPages.js | 5 + .../features/pages/restoreWalletPage.js | 5 +- .../features/pages/settingsPage.js | 15 +++ .../features/pages/uriPromptPage.js | 8 ++ .../features/pages/walletDashboardPage.js | 9 ++ .../features/pages/walletPage.js | 6 +- .../features/pages/walletReceivePage.js | 11 +- .../addresses-generation-steps.js | 24 ++-- .../features/step_definitions/common-steps.js | 112 +++++++++--------- .../daedalus-transfer-steps.js | 15 ++- .../step_definitions/dashboard-steps.js | 12 +- .../general-settings-steps.js | 16 ++- .../step_definitions/hardware-steps.js | 23 ++-- .../step_definitions/select-language-steps.js | 3 +- .../step_definitions/settings-ui-steps.js | 3 +- .../step_definitions/wallet-creation-steps.js | 3 +- .../wallet-restoration-steps.js | 31 ++--- .../features/step_definitions/wallet-steps.js | 5 +- .../helpers/language-selection-helpers.js | 5 +- 22 files changed, 212 insertions(+), 125 deletions(-) create mode 100644 packages/yoroi-extension/features/pages/basicSetupPage.js create mode 100644 packages/yoroi-extension/features/pages/daedalusTransferPage.js create mode 100644 packages/yoroi-extension/features/pages/mainWindowPage.js create mode 100644 packages/yoroi-extension/features/pages/settingsPage.js create mode 100644 packages/yoroi-extension/features/pages/uriPromptPage.js create mode 100644 packages/yoroi-extension/features/pages/walletDashboardPage.js diff --git a/packages/yoroi-extension/features/pages/basicSetupPage.js b/packages/yoroi-extension/features/pages/basicSetupPage.js new file mode 100644 index 0000000000..7cf2f79b09 --- /dev/null +++ b/packages/yoroi-extension/features/pages/basicSetupPage.js @@ -0,0 +1,13 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +// language select page +export const languageSelectionForm: LocatorObject = { locator: '.LanguageSelectionForm_component', method: 'css' }; + + +export const continueButton: LocatorObject = { locator: '//button[text()="Continue"]', method: 'xpath' }; +// ToS page +export const termsOfUseComponent: LocatorObject = { locator: '.TermsOfUseForm_component', method: 'css' }; +// uri prompt page +export const walletAddComponent: LocatorObject ={ locator: '.WalletAdd_component', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/daedalusTransferPage.js b/packages/yoroi-extension/features/pages/daedalusTransferPage.js new file mode 100644 index 0000000000..f04a4cfc80 --- /dev/null +++ b/packages/yoroi-extension/features/pages/daedalusTransferPage.js @@ -0,0 +1,8 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const nextButton: LocatorObject = { locator: "//button[contains(@label, 'Next')]", method: 'xpath' }; +export const backButton: LocatorObject = { locator: "//button[contains(@label, 'Back')]", method: 'xpath' }; +export const formFieldOverridesClassicError: LocatorObject = { locator: '.FormFieldOverridesClassic_error', method: 'css' }; +export const transferButton: LocatorObject = { locator: '.transferButton', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/mainWindowPage.js b/packages/yoroi-extension/features/pages/mainWindowPage.js new file mode 100644 index 0000000000..d80f6e1609 --- /dev/null +++ b/packages/yoroi-extension/features/pages/mainWindowPage.js @@ -0,0 +1,5 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const yoroiClassic: LocatorObject = { locator: '.YoroiClassic', method: 'css' } \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/newWalletPages.js b/packages/yoroi-extension/features/pages/newWalletPages.js index 27a5240574..473a61f47f 100644 --- a/packages/yoroi-extension/features/pages/newWalletPages.js +++ b/packages/yoroi-extension/features/pages/newWalletPages.js @@ -7,6 +7,11 @@ export const createWalletButton: LocatorObject = { locator: '.WalletAdd_btnCreat export const restoreWalletButton: LocatorObject = { locator: '.WalletAdd_btnRestoreWallet', method: 'css' }; // Currency options dialog export const pickUpCurrencyDialog: LocatorObject = { locator: '.PickCurrencyOptionDialog', method: 'css' }; +export const pickUpCurrencyDialogErgo: LocatorObject = { locator: '.PickCurrencyOptionDialog_ergo', method: 'css' }; +export const pickUpCurrencyDialogCardano: LocatorObject = { locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }; +export const walletRestoreOptionDialog = { locator: '.WalletRestoreOptionDialog', method: 'css' }; +export const restoreNormalWallet = { locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }; +export const walletRestoreDialog = { locator: '.WalletRestoreDialog', method: 'css' }; export const getCurrencyButton = (currency: string): LocatorObject => { return { locator: `.PickCurrencyOptionDialog_${currency}`, method: 'css' }; }; diff --git a/packages/yoroi-extension/features/pages/restoreWalletPage.js b/packages/yoroi-extension/features/pages/restoreWalletPage.js index 1763940354..b2a43a4fb7 100644 --- a/packages/yoroi-extension/features/pages/restoreWalletPage.js +++ b/packages/yoroi-extension/features/pages/restoreWalletPage.js @@ -26,6 +26,9 @@ export const getWords = (word: string): LocatorObject => { return { locator: `//span[contains(text(), '${word}')]`, method: 'xpath' } }; +export const walletNameInput = { locator: "input[name='walletName']", method: 'css' }; +export const restoreWalletButton = { locator: '.WalletRestoreDialog .primary', method: 'css' } export const walletPasswordInput = { locator: "input[name='walletPassword']", method: 'css' }; export const repeatPasswordInput = { locator: "input[name='repeatPassword']", method: 'css' }; -export const paperPasswordInput = { locator: "input[name='paperPassword']", method: 'css' }; \ No newline at end of file +export const paperPasswordInput = { locator: "input[name='paperPassword']", method: 'css' }; +export const confirmButton = { locator: '.confirmButton', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/settingsPage.js b/packages/yoroi-extension/features/pages/settingsPage.js new file mode 100644 index 0000000000..2ec9e1b36f --- /dev/null +++ b/packages/yoroi-extension/features/pages/settingsPage.js @@ -0,0 +1,15 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const settingsLayoutComponent: LocatorObject = { locator: '.SettingsLayout_component', method: 'css' }; +export const secondThemeSelected: LocatorObject = { + locator: '.ThemeSettingsBlock_themesWrapper button:nth-child(2).ThemeSettingsBlock_active', + method: 'css' + }; + + export const complexityLevelForm: LocatorObject = { locator: '.ComplexityLevelForm_cardsWrapper', method: 'css' }; + export const complexitySelected: LocatorObject = { locator: '.currentLevel', method: 'css' }; + export const languageSelector: LocatorObject = { locator: '//div[starts-with(@id, "languageId")]', method: 'xpath' }; + + diff --git a/packages/yoroi-extension/features/pages/uriPromptPage.js b/packages/yoroi-extension/features/pages/uriPromptPage.js new file mode 100644 index 0000000000..88de4cdbe8 --- /dev/null +++ b/packages/yoroi-extension/features/pages/uriPromptPage.js @@ -0,0 +1,8 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const uriPromptForm: LocatorObject = { locator: '.UriPromptForm_component', method: 'css' }; +export const allowButton: LocatorObject = { locator: '.allowButton', method: 'css' }; +export const uriAcceptComponent: LocatorObject = { locator: '.UriAccept_component', method: 'css' }; +export const finishButton: LocatorObject = { locator: '.finishButton', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/walletDashboardPage.js b/packages/yoroi-extension/features/pages/walletDashboardPage.js new file mode 100644 index 0000000000..aa61194807 --- /dev/null +++ b/packages/yoroi-extension/features/pages/walletDashboardPage.js @@ -0,0 +1,9 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const withdrawButton: LocatorObject = { locator: '.withdrawButton', method: 'css' }; +export const rechartBar: LocatorObject = { locator: '.recharts-bar', method: 'css' }; + +export const mangledWarningIcon: LocatorObject = { locator: '.UserSummary_mangledWarningIcon', method: 'css' }; +export const userSummaryLink: LocatorObject = { locator: '.UserSummary_link', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletPage.js b/packages/yoroi-extension/features/pages/walletPage.js index 4f80c90d2c..e1a0f7bb8c 100644 --- a/packages/yoroi-extension/features/pages/walletPage.js +++ b/packages/yoroi-extension/features/pages/walletPage.js @@ -4,4 +4,8 @@ export const summaryTab = { locator: 'summary', method: 'css' }; export const sendTab = { locator: '.send', method: 'css' }; export const receiveTab = { locator: '.receive', method: 'css' }; -export const claimTransferTab = { locator: '.claimTransfer', method: 'css' }; \ No newline at end of file +export const claimTransferTab = { locator: '.claimTransfer', method: 'css' }; + +export const walletNameText = { locator: '.NavPlate_name', method: 'css' }; +export const activeNavTab = { locator: '.WalletNavButton_active', method: 'css' }; +export const dashboardTab = { locator: '.stakeDashboard ', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/walletReceivePage.js b/packages/yoroi-extension/features/pages/walletReceivePage.js index 6ad8a1cccc..387bb35067 100644 --- a/packages/yoroi-extension/features/pages/walletReceivePage.js +++ b/packages/yoroi-extension/features/pages/walletReceivePage.js @@ -5,16 +5,19 @@ import type { LocatorObject } from '../support/webdriver'; export const getGeneratedAddressLocator = (rowIndex: number): LocatorObject => { return { locator: `.generatedAddress-${rowIndex + 1} .RawHash_hash`, - method: 'css' + method: 'css', }; }; export const getAddressLocator = (address: string): LocatorObject => { return { locator: `//div[contains(text(), "${address}")]`, - method: 'xpath' + method: 'xpath', }; -} +}; export const addressErrorPhrase = { locator: '.StandardHeader_error', method: 'css' }; -export const generateAddressButton = { locator: '.generateAddressButton', method: 'css' }; \ No newline at end of file +export const generateAddressButton = { locator: '.generateAddressButton', method: 'css' }; +export const addressBookTab = { locator: `.addressBook`, method: 'css' }; +export const rewardAddressTab = { locator: `.reward`, method: 'css' }; +export const yourWalletAddrHeader = { locator: '.StandardHeader_copyableHash', method: 'css' }; diff --git a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js index 7a738f6b87..eff4c477ac 100644 --- a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js @@ -8,9 +8,13 @@ import { checkIfElementsInArrayAreUnique } from '../support/helpers/helpers'; import { truncateAddress, truncateAddressShort } from '../../app/utils/formatters'; import { receiveTab } from '../pages/walletPage'; import { - addressErrorPhrase, generateAddressButton, + addressErrorPhrase, + generateAddressButton, getAddressLocator, getGeneratedAddressLocator, + addressBookTab, + rewardAddressTab, + yourWalletAddrHeader } from '../pages/walletReceivePage'; Given(/^Revamp. I go to the receive screen$/, async function () { @@ -18,7 +22,7 @@ Given(/^Revamp. I go to the receive screen$/, async function () { }); Given(/^I go to the receive screen$/, async function () { - await this.click({ locator: '.receive', method: 'css' }); + await this.click(receiveTab); }); When(/^I click on the Generate new address button$/, async function () { @@ -30,17 +34,11 @@ When(/^I click on the ([^ ]*) ([^ ]*) tab$/, async function (kind, chain) { }); When(/^I click on the top-level address book tab$/, async function () { - await this.click({ - locator: `//div[contains(text(), "Address book") and contains(@class, "ReceiveNavButton_label")]`, - method: 'xpath', - }); + await this.click(addressBookTab); }); When(/^I click on the top-level reward address tab$/, async function () { - await this.click({ - locator: `//div[contains(text(), "Reward") and contains(@class, "ReceiveNavButton_label")]`, - method: 'xpath', - }); + await this.click(rewardAddressTab); }); When(/^I click on the All addresses button$/, async function () { @@ -70,7 +68,7 @@ When(/^I click on the HasBalance addresses button$/, async function () { When(/^I click on the Generate new address button ([0-9]+) times$/, async function (times) { for (let curr = 1; curr <= times; curr++) { - await this.click({ locator: '.generateAddressButton', method: 'css' }); + await this.click(generateAddressButton); await this.waitForElement({ locator: `.generatedAddress-${curr + 1} .RawHash_hash`, method: 'css', @@ -80,14 +78,16 @@ When(/^I click on the Generate new address button ([0-9]+) times$/, async functi Then(/^I should see my latest address "([^"]*)" at the top$/, async function (address) { await this.waitUntilText( - { locator: '.StandardHeader_copyableHash', method: 'css' }, + yourWalletAddrHeader, truncateAddress(address) ); }); + Then(/^I should see at least ([^"]*) addresses$/, async function (numAddresses) { const rows = await this.driver.findElements(By.css('.WalletReceive_walletAddress')); expect(rows.length).be.at.least(Number.parseInt(numAddresses, 10)); }); + Then( /^I should see ([^"]*) addresses with address "([^"]*)" at the top$/, async function (numAddresses, address) { diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 5553002f5a..62fe626015 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -17,10 +17,7 @@ import { enterRecoveryPhrase, getLogDate } from '../support/helpers/helpers'; import { testWallets } from '../mock-chain/TestWallets'; import * as ErgoImporter from '../mock-chain/mockErgoImporter'; import * as CardanoImporter from '../mock-chain/mockCardanoImporter'; -import { - testRunsDataDir, - snapshotsDir, - } from '../support/helpers/common-constants'; +import { testRunsDataDir, snapshotsDir } from '../support/helpers/common-constants'; import { expect } from 'chai'; import { satisfies } from 'semver'; // eslint-disable-next-line import/named @@ -50,10 +47,26 @@ import { trezorConfirmButton, walletNameInput, saveDialog, - saveButton + saveButton, + pickUpCurrencyDialogErgo, + walletRestoreOptionDialog, + restoreNormalWallet, + walletRestoreDialog, + pickUpCurrencyDialogCardano, + byronEraButton, } from '../pages/newWalletPages'; import { allowPubKeysAndSwitchToYoroi, switchToTrezorAndAllow } from './trezor-steps'; import * as helpers from '../support/helpers/helpers'; +import { + confirmButton, + repeatPasswordInput, + walletPasswordInput, +} from '../pages/restoreWalletPage'; +import { walletNameText } from '../pages/walletPage'; +import { continueButton, languageSelectionForm, termsOfUseComponent, walletAddComponent } from '../pages/basicSetupPage'; +import { settingsLayoutComponent } from '../pages/settingsPage'; +import { allowButton, finishButton, uriAcceptComponent, uriPromptForm } from '../pages/uriPromptPage'; +import { yoroiClassic } from '../pages/mainWindowPage'; const { promisify } = require('util'); const fs = require('fs'); @@ -259,27 +272,18 @@ async function inputMnemonicForWallet( walletName: string, restoreInfo: RestorationInput ): Promise { - await customWorld.input({ locator: "input[name='walletName']", method: 'css' }, restoreInfo.name); + await customWorld.input(walletNameInput, restoreInfo.name); await enterRecoveryPhrase(customWorld, restoreInfo.mnemonic); - await customWorld.input( - { locator: "input[name='walletPassword']", method: 'css' }, - restoreInfo.password - ); - await customWorld.input( - { locator: "input[name='repeatPassword']", method: 'css' }, - restoreInfo.password - ); - await customWorld.click({ locator: '.WalletRestoreDialog .primary', method: 'css' }); + await customWorld.input(walletPasswordInput, restoreInfo.password); + await customWorld.input(repeatPasswordInput, restoreInfo.password); + await customWorld.click(restoreWalletButton); const plateElements = await getPlates(customWorld); const plateText = await plateElements[0].getText(); expect(plateText).to.be.equal(restoreInfo.plate); - await customWorld.click({ locator: '.confirmButton', method: 'css' }); - await customWorld.waitUntilText( - { locator: '.NavPlate_name', method: 'css' }, - truncateLongName(walletName) - ); + await customWorld.click(confirmButton); + await customWorld.waitUntilText(walletNameText, truncateLongName(walletName)); } export async function checkErrorByTranslationId( @@ -300,15 +304,15 @@ Given(/^There is an Ergo wallet stored named ([^"]*)$/, async function (walletNa const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); - await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); + await this.click(restoreWalletButton); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_ergo', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogErgo); - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); + await this.waitForElement(walletRestoreOptionDialog); - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.waitForElement(walletRestoreDialog); await inputMnemonicForWallet(this, walletName, restoreInfo); }); @@ -318,16 +322,16 @@ Given(/^There is a Shelley wallet stored named ([^"]*)$/, async function (wallet const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); - await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); + await this.click(restoreWalletButton); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); + await this.waitForElement(walletRestoreOptionDialog); - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.click(shelleyEraButton); + await this.waitForElement(walletRestoreDialog); await inputMnemonicForWallet(this, walletName, restoreInfo); }); @@ -342,11 +346,11 @@ Given(/^There is a Byron wallet stored named ([^"]*)$/, async function (walletNa await this.waitForElement(pickUpCurrencyDialog); await this.click(getCurrencyButton('cardano')); - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); + await this.waitForElement(walletRestoreOptionDialog); - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.click(byronEraButton); + await this.waitForElement(walletRestoreDialog); await inputMnemonicForWallet(this, walletName, restoreInfo); }); @@ -354,17 +358,17 @@ Given(/^There is a Byron wallet stored named ([^"]*)$/, async function (walletNa Given(/^I have completed the basic setup$/, async function () { this.webDriverLogger.info(`Step: I have completed the basic setup`); // language select page - await this.waitForElement({ locator: '.LanguageSelectionForm_component', method: 'css' }); - await this.click({ locator: '//button[text()="Continue"]', method: 'xpath' }); + await this.waitForElement(languageSelectionForm); + await this.click(); // ToS page - await this.waitForElement({ locator: '.TermsOfUseForm_component', method: 'css' }); + await this.waitForElement(termsOfUseComponent); const tosClassElement = await this.driver.findElement(By.css('.TermsOfUseForm_component')); const checkbox = await tosClassElement.findElement(By.xpath('//input[@type="checkbox"]')); await checkbox.click(); - await this.click({ locator: '//button[text()="Continue"]', method: 'xpath' }); + await this.click(continueButton); // uri prompt page await acceptUriPrompt(this); - await this.waitForElement({ locator: '.WalletAdd_component', method: 'css' }); + await this.waitForElement(walletAddComponent); }); Given(/^I switched to the advanced level$/, async function () { @@ -373,7 +377,7 @@ Given(/^I switched to the advanced level$/, async function () { await navigateTo.call(this, '/settings'); await navigateTo.call(this, '/settings/general'); await waitUntilUrlEquals.call(this, '/settings/general'); - await this.waitForElement({ locator: '.SettingsLayout_component', method: 'css' }); + await this.waitForElement(settingsLayoutComponent); // Click on secondary menu "levelOfComplexity" item await selectSubmenuSettings(this, 'levelOfComplexity'); // Select the most complex level @@ -383,7 +387,7 @@ Given(/^I switched to the advanced level$/, async function () { // Navigate back to the main page await navigateTo.call(this, '/wallets/add'); await waitUntilUrlEquals.call(this, '/wallets/add'); - await this.waitForElement({ locator: '.WalletAdd_component', method: 'css' }); + await this.waitForElement(walletAddComponent); }); Then(/^I accept uri registration$/, async function () { @@ -393,10 +397,10 @@ Then(/^I accept uri registration$/, async function () { async function acceptUriPrompt(world: any) { if (world.getBrowser() !== 'firefox') { - await world.waitForElement({ locator: '.UriPromptForm_component', method: 'css' }); - await world.click({ locator: '.allowButton', method: 'css' }); - await world.waitForElement({ locator: '.UriAccept_component', method: 'css' }); - await world.click({ locator: '.finishButton', method: 'css' }); + await world.waitForElement(uriPromptForm); + await world.click(allowButton); + await world.waitForElement(uriAcceptComponent); + await world.click(finishButton); } } @@ -414,7 +418,7 @@ Given(/^I refresh the page$/, async function () { await this.driver.navigate().refresh(); // wait for page to refresh await this.driver.sleep(500); - await this.waitForElement({ locator: '.YoroiClassic', method: 'css' }); + await this.waitForElement(yoroiClassic); }); Given(/^I restart the browser$/, async function () { @@ -423,13 +427,13 @@ Given(/^I restart the browser$/, async function () { await this.driver.navigate().refresh(); // wait for page to refresh await this.driver.sleep(500); - await this.waitForElement({ locator: '.YoroiClassic', method: 'css' }); + await this.waitForElement(yoroiClassic); }); Given(/^There is no wallet stored$/, async function () { this.webDriverLogger.info(`Step: There is no wallet stored`); await restoreWalletsFromStorage(this); - await this.waitForElement({ locator: '.WalletAdd_component', method: 'css' }); + await this.waitForElement(walletAddComponent); }); Then(/^I click then button labeled (.*)$/, async function (buttonName) { @@ -450,7 +454,7 @@ Given(/^I import a snapshot named ([^"]*)$/, async function (snapshotName) { await this.driver.navigate().refresh(); // wait for page to refresh await this.driver.sleep(1500); - await this.waitForElement({ locator: '.YoroiClassic', method: 'css' }); + await this.waitForElement(yoroiClassic); }); async function setLedgerWallet(client, serial) { @@ -661,7 +665,7 @@ Then(/^Revamp. I go to the wallet ([^"]*)$/, async function (walletName) { await walletButtonInRow.click(); }); -Then(/^Debug. Take screenshot$/, async function () { +Then(/^Debug. Take screenshot$/, async function () { const currentTime = getLogDate(); await takeScreenshot(this.driver, `debug_${currentTime}`); await takePageSnapshot(this.driver, `debug_${currentTime}`); @@ -671,4 +675,4 @@ Then(/^Debug. Take screenshot$/, async function () { Then(/^Debug. Make driver sleep for 2 seconds$/, async function () { await this.driver.sleep(2000); -}); \ No newline at end of file +}); diff --git a/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js b/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js index cae94a3482..87d7dfddcc 100644 --- a/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js +++ b/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js @@ -20,6 +20,9 @@ import { daedalusMasterKeyButton, twelveWordOption } from '../pages/walletClaimT import { proceedRecoveryButton } from '../pages/restoreWalletPage'; import { errorMessage, errorPageTitle } from '../pages/errorPage'; import { amountField, feeField, totalAmountField } from '../pages/confirmTransactionPage'; +import { walletAddComponent } from '../pages/basicSetupPage'; +import { backButton, formFieldOverridesClassicError, nextButton, transferButton } from '../pages/daedalusTransferPage'; +import { activeNavTab } from '../pages/walletPage'; Before({ tags: '@withWebSocketConnection' }, () => { closeMockServer(); @@ -61,33 +64,33 @@ When(/^I proceed with the recovery$/, async function () { }); When(/^I click next button on the Daedalus transfer page$/, async function () { - await this.click({ locator: "//button[contains(@label, 'Next')]", method: 'xpath' }); + await this.click(nextButton); }); When(/^I click the back button$/, async function () { - await this.click({ locator: "//button[contains(@label, 'Back')]", method: 'xpath' }); + await this.click(backButton); }); Then(/^I should see "This field is required." error message:$/, async function (data) { const error = data.hashes()[0]; await checkErrorByTranslationId( this, - { locator: '.FormFieldOverridesClassic_error', method: 'css' }, + formFieldOverridesClassicError, error); }); When(/^I confirm Daedalus transfer funds$/, async function () { - await this.click({ locator: '.transferButton', method: 'css' }); + await this.click(transferButton); }); Then(/^I should see the Create wallet screen$/, async function () { - await this.waitForElement({ locator: '.WalletAdd_component', method: 'css' }); + await this.waitForElement(walletAddComponent); }); Then(/^I should see the Receive screen$/, async function () { const receiveTitle = await i18n.formatMessage(this.driver, { id: 'wallet.navigation.receive' }); - await this.waitUntilText({ locator: '.WalletNavButton_active', method: 'css' }, receiveTitle); + await this.waitUntilText(activeNavTab, receiveTitle); await this.driver.sleep(2000); }); diff --git a/packages/yoroi-extension/features/step_definitions/dashboard-steps.js b/packages/yoroi-extension/features/step_definitions/dashboard-steps.js index 3dd63b753e..162ec3f647 100644 --- a/packages/yoroi-extension/features/step_definitions/dashboard-steps.js +++ b/packages/yoroi-extension/features/step_definitions/dashboard-steps.js @@ -1,20 +1,22 @@ // @flow import { Then, When, } from 'cucumber'; +import { mangledWarningIcon, rechartBar, userSummaryLink, withdrawButton } from '../pages/walletDashboardPage'; +import { dashboardTab } from '../pages/walletPage'; When(/^I go to the dashboard screen$/, async function () { - await this.click({ locator: '.stakeDashboard ', method: 'css' }); + await this.click(dashboardTab); }); When(/^I click on the withdraw button$/, async function () { - await this.click({ locator: '.withdrawButton', method: 'css' }); + await this.click(withdrawButton); }); Then(/^I should rewards in the history$/, async function () { - await this.waitForElement({ locator: '.recharts-bar', method: 'css' }); + await this.waitForElement(rechartBar); }); When(/^I click on the unmangle warning$/, async function () { - await this.click({ locator: '.UserSummary_mangledWarningIcon', method: 'css' }); - await this.click({ locator: '.UserSummary_link', method: 'css' }); + await this.click(mangledWarningIcon); + await this.click(userSummaryLink); }); diff --git a/packages/yoroi-extension/features/step_definitions/general-settings-steps.js b/packages/yoroi-extension/features/step_definitions/general-settings-steps.js index a3ca6d8be3..f9507872fa 100644 --- a/packages/yoroi-extension/features/step_definitions/general-settings-steps.js +++ b/packages/yoroi-extension/features/step_definitions/general-settings-steps.js @@ -5,6 +5,7 @@ import { camelCase } from 'lodash'; import { waitUntilUrlEquals, navigateTo } from '../support/helpers/route-helpers'; import i18n from '../support/helpers/i18n-helpers'; import { By, WebElement } from 'selenium-webdriver'; +import { complexitySelected, secondThemeSelected, settingsLayoutComponent, complexityLevelForm, languageSelector } from '../pages/settingsPage'; export async function selectSubmenuSettings(customWorld: Object, buttonName: string) { const formattedButtonName = camelCase(buttonName); @@ -20,14 +21,14 @@ export async function goToSettings(customWorld: Object) { await navigateTo.call(customWorld, '/settings/general'); await waitUntilUrlEquals.call(customWorld, '/settings/general'); - await customWorld.waitForElement({ locator: '.SettingsLayout_component', method: 'css' }); + await customWorld.waitForElement(settingsLayoutComponent); } export async function getComplexityLevelButton( customWorld: Object, isLow: boolean = true ): Promise { - await customWorld.waitForElement({ locator: '.ComplexityLevelForm_cardsWrapper', method: 'css' }); + await customWorld.waitForElement(complexityLevelForm); const levels = await customWorld.driver.findElements(By.css('.ComplexityLevelForm_card')); let card; if (isLow) { @@ -50,11 +51,11 @@ When(/^I click on secondary menu "([^"]*)" item$/, async function (buttonName) { }); When(/^I select second theme$/, async function () { - await this.click({ locator: '.ThemeSettingsBlock_themesWrapper > button:nth-child(2)', method: 'css' }); + await this.click(secondThemeSelected); }); When(/^I open General Settings language selection dropdown$/, async function () { - await this.click({ locator: '//div[starts-with(@id, "languageId")]', method: 'xpath' }); + await this.click(languageSelector); }); Then(/^I should see secondary menu (.*) item disabled$/, async function (buttonName) { @@ -74,14 +75,11 @@ Then(/^The Japanese language should be selected$/, async function () { }); Then(/^I should see second theme as selected$/, async function () { - await this.waitForElement({ - locator: '.ThemeSettingsBlock_themesWrapper button:nth-child(2).ThemeSettingsBlock_active', - method: 'css' - }); + await this.waitForElement(secondThemeSelected); }); Then(/^The selected level is "([^"]*)"$/, async function (level) { - await this.waitUntilText({ locator: '.currentLevel', method: 'css' }, level.toUpperCase()); + await this.waitUntilText(complexitySelected, level.toUpperCase()); }); Then(/^I select the most complex level$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/hardware-steps.js b/packages/yoroi-extension/features/step_definitions/hardware-steps.js index d2e20e5cd9..abbc5fc42f 100644 --- a/packages/yoroi-extension/features/step_definitions/hardware-steps.js +++ b/packages/yoroi-extension/features/step_definitions/hardware-steps.js @@ -5,28 +5,29 @@ import { testWallets } from '../mock-chain/TestWallets'; import { truncateAddress, } from '../../app/utils/formatters'; import { addressField, derivationField, verifyButton } from '../pages/verifyAddressPage'; import { expect } from 'chai'; +import { byronEraButton, pickUpCurrencyDialog, pickUpCurrencyDialogCardano, shelleyEraButton } from '../pages/newWalletPages'; When(/^I select a Byron-era Ledger device$/, async function () { await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); await this.click({ locator: '.WalletConnectHWOptionDialog_connectLedger', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }); + await this.click(byronEraButton); }); When(/^I select a Shelley-era Ledger device$/, async function () { await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); await this.click({ locator: '.WalletConnectHWOptionDialog_connectLedger', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }); + await this.click(shelleyEraButton); }); When(/^I restore the Ledger device$/, async function () { await this.waitForElement({ locator: '.CheckDialog_component', method: 'css' }); @@ -43,19 +44,19 @@ When(/^I restore the Ledger device$/, async function () { When(/^I select a Byron-era Trezor device$/, async function () { await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); await this.click({ locator: '.WalletConnectHWOptionDialog_connectTrezor', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }); + await this.click(byronEraButton); }); When(/^I select a Shelley-era Trezor device$/, async function () { await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); diff --git a/packages/yoroi-extension/features/step_definitions/select-language-steps.js b/packages/yoroi-extension/features/step_definitions/select-language-steps.js index 6acea5450c..7e9a73e191 100644 --- a/packages/yoroi-extension/features/step_definitions/select-language-steps.js +++ b/packages/yoroi-extension/features/step_definitions/select-language-steps.js @@ -3,6 +3,7 @@ import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; import languageSelection, { clickContinue } from '../support/helpers/language-selection-helpers'; +import { languageSelectionForm } from '../pages/basicSetupPage'; const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; @@ -11,7 +12,7 @@ Given(/^I have selected English language$/, async function () { }); When(/^I am on the language selection screen$/, async function () { - await this.waitForElement({ locator: LANGUAGE_SELECTION_FORM, method: 'css' }); + await this.waitForElement(languageSelectionForm); }); When(/^I open language selection dropdown$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js index 9567bda029..ff54f9924c 100644 --- a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js @@ -6,6 +6,7 @@ import { By, Key } from 'selenium-webdriver'; import { truncateLongName, } from '../../app/utils/formatters'; import { expect } from 'chai'; import { checkErrorByTranslationId } from './common-steps'; +import { walletNameText } from '../pages/walletPage'; const walletNameInputSelector = '.SettingsLayout_settingsPane .walletName input'; @@ -79,7 +80,7 @@ Then(/^I should not see the change password dialog anymore$/, async function () }); Then(/^I should see new wallet name "([^"]*)"$/, async function (walletName) { - await this.waitUntilText({ locator: '.NavPlate_name', method: 'css' }, truncateLongName(walletName)); + await this.waitUntilText(walletNameText, truncateLongName(walletName)); }); Then(/^I should see the following error messages:$/, async function (data) { diff --git a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js index 3a598eebc8..5c9234b8cc 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js @@ -10,6 +10,7 @@ import { getCurrencyButton, pickUpCurrencyDialog } from '../pages/newWalletPages'; +import { continueButton } from '../pages/basicSetupPage'; When(/^I click the create button$/, async function () { await this.click(createWalletButton); @@ -64,7 +65,7 @@ When(/^I accept the creation terms$/, async function () { ); const privacyChkbox = privacyDlg.findElement(By.xpath('//input[@type="checkbox"]')); privacyChkbox.click(); - await this.click({ locator: '//button[text()="Continue"]', method: 'xpath' }); + await this.click(continueButton); }); When(/^I copy and enter the displayed mnemonic phrase$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js index 511ee8506b..687be13eab 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js @@ -16,46 +16,47 @@ import { walletPasswordInput, } from '../pages/restoreWalletPage'; import { masterKeyInput } from '../pages/walletClaimTransferPage'; +import { pickUpCurrencyDialog, pickUpCurrencyDialogCardano, restoreNormalWallet, shelleyEraButton, walletRestoreDialog, walletRestoreOptionDialog } from '../pages/newWalletPages'; When(/^I click the restore button for ([^"]*)$/, async function (currency) { await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); await this.click({ locator: `.PickCurrencyOptionDialog_${currency}`, method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); + await this.waitForElement(walletRestoreOptionDialog); }); Then(/^I select Byron-era 15-word wallet$/, async function () { - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.click(byronEraButton); + await this.waitForElement(walletRestoreDialog); }); Then(/^I select Shelley-era 15-word wallet$/, async function () { - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.click(shelleyEraButton); + await this.waitForElement(walletRestoreDialog); }); Then(/^I select Shelley-era 24-word wallet$/, async function () { await this.click({ locator: '.WalletRestoreOptionDialog_normal24WordWallet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.waitForElement(walletRestoreDialog); }); Then(/^I select bip44 15-word wallet$/, async function () { - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.waitForElement(walletRestoreDialog); }); When(/^I click the restore paper wallet button$/, async function () { await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); + await this.waitForElement(walletRestoreOptionDialog); await this.click({ locator: '.WalletRestoreOptionDialog_restorePaperWallet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.waitForElement(walletRestoreDialog); }); When(/^I enter the recovery phrase:$/, async function (table) { diff --git a/packages/yoroi-extension/features/step_definitions/wallet-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-steps.js index c9bc69616c..a9a1b9e5b7 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-steps.js @@ -2,6 +2,7 @@ import { When, Then } from 'cucumber'; import { truncateLongName } from '../../app/utils/formatters'; +import { walletNameText } from '../pages/walletPage'; When(/^I enter the name "([^"]*)"$/, async function (walletName) { await this.input({ locator: "input[name='walletName']", method: 'css' }, walletName); @@ -13,11 +14,11 @@ When(/^I clear the name "([^"]*)"$/, async function (walletName) { When(/^I navigate to wallet sidebar category$/, async function () { await this.click({ locator: `//div[@class='Sidebar_categories']//button[1]`, method: 'xpath' }); - await this.waitForElement({ locator: '.NavPlate_name', method: 'css' }); + await this.waitForElement(walletNameText); }); Then(/^I should see the opened wallet with name "([^"]*)"$/, async function (walletName) { - await this.waitUntilText({ locator: '.NavPlate_name', method: 'css' }, truncateLongName(walletName)); + await this.waitUntilText(walletNameText, truncateLongName(walletName)); }); Then(/^I unselect the wallet$/, async function () { diff --git a/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js b/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js index 7d031f377d..d22abd7cf9 100644 --- a/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js +++ b/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js @@ -2,6 +2,7 @@ import i18n from './i18n-helpers'; import { By } from 'selenium-webdriver'; +import { languageSelectionForm } from '../../pages/basicSetupPage'; const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; @@ -11,9 +12,9 @@ const languageSelection = { { isHidden }: {| isHidden: boolean, |} = {} ): Promise => { if (isHidden) { - return client.waitForElementNotPresent({ locator: LANGUAGE_SELECTION_FORM, method: 'css' }); + return client.waitForElementNotPresent(languageSelectionForm); } - return client.waitForElement({ locator: LANGUAGE_SELECTION_FORM, method: 'css' }); + return client.waitForElement(languageSelectionForm); }, ensureLanguageIsSelected: async ( client: any, From d625a4c380b14a8ae394651592ccd9fa8bb2d0fe Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Mon, 29 Aug 2022 18:10:53 -0300 Subject: [PATCH 061/199] Moved more locators to corresponding pages --- .../features/pages/basicSetupPage.js | 22 +++- .../features/pages/commonDialogPage.js | 7 + .../features/pages/mainWindowPage.js | 4 +- .../features/pages/newWalletPages.js | 4 +- .../features/pages/settingsPage.js | 96 +++++++++++++- .../features/pages/sidebarPage.js | 6 +- .../features/pages/walletPage.js | 9 +- .../features/pages/walletReceivePage.js | 3 + .../features/pages/walletSendPage.js | 6 + .../features/pages/walletTransactionsPage.js | 4 +- .../step_definitions/hardware-steps.js | 90 +++++++------ .../installation-procedure-steps.js | 8 +- .../step_definitions/main-ui-steps.js | 64 +++++---- .../features/step_definitions/memo-steps.js | 28 ++-- .../step_definitions/select-language-steps.js | 9 +- .../step_definitions/settings-ui-steps.js | 121 ++++++++++-------- .../step_definitions/transactions-steps.js | 11 +- .../features/step_definitions/trezor-steps.js | 3 +- .../features/step_definitions/uri-steps.js | 3 +- .../step_definitions/wallet-creation-steps.js | 3 +- .../step_definitions/wallet-paper-steps.js | 3 +- .../wallet-restoration-steps.js | 6 +- .../features/step_definitions/wallet-steps.js | 5 +- .../step_definitions/yoroi-transfer-steps.js | 3 +- 24 files changed, 348 insertions(+), 170 deletions(-) create mode 100644 packages/yoroi-extension/features/pages/commonDialogPage.js create mode 100644 packages/yoroi-extension/features/pages/walletSendPage.js diff --git a/packages/yoroi-extension/features/pages/basicSetupPage.js b/packages/yoroi-extension/features/pages/basicSetupPage.js index 7cf2f79b09..381f53a890 100644 --- a/packages/yoroi-extension/features/pages/basicSetupPage.js +++ b/packages/yoroi-extension/features/pages/basicSetupPage.js @@ -3,11 +3,23 @@ import type { LocatorObject } from '../support/webdriver'; // language select page -export const languageSelectionForm: LocatorObject = { locator: '.LanguageSelectionForm_component', method: 'css' }; +export const languageSelectionForm: LocatorObject = { + locator: '.LanguageSelectionForm_component', + method: 'css', +}; +export const japaneseLaguageSelection: LocatorObject = { + locator: '//span[contains(text(), "日本語")]', + method: 'xpath', +}; - -export const continueButton: LocatorObject = { locator: '//button[text()="Continue"]', method: 'xpath' }; +export const continueButton: LocatorObject = { + locator: '//button[text()="Continue"]', + method: 'xpath', +}; // ToS page -export const termsOfUseComponent: LocatorObject = { locator: '.TermsOfUseForm_component', method: 'css' }; +export const termsOfUseComponent: LocatorObject = { + locator: '.TermsOfUseForm_component', + method: 'css', +}; // uri prompt page -export const walletAddComponent: LocatorObject ={ locator: '.WalletAdd_component', method: 'css' }; \ No newline at end of file +export const walletAddComponent: LocatorObject = { locator: '.WalletAdd_component', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/commonDialogPage.js b/packages/yoroi-extension/features/pages/commonDialogPage.js new file mode 100644 index 0000000000..242f03173f --- /dev/null +++ b/packages/yoroi-extension/features/pages/commonDialogPage.js @@ -0,0 +1,7 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const primaryButton: LocatorObject = { locator: '.primary', method: 'css' }; +export const errorBlockComponent: LocatorObject = { locator: '.ErrorBlock_component', method: 'css' }; +export const dialogTitle = { locator: '.dialog__title', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/mainWindowPage.js b/packages/yoroi-extension/features/pages/mainWindowPage.js index d80f6e1609..c2e14ce0e2 100644 --- a/packages/yoroi-extension/features/pages/mainWindowPage.js +++ b/packages/yoroi-extension/features/pages/mainWindowPage.js @@ -2,4 +2,6 @@ import type { LocatorObject } from '../support/webdriver'; -export const yoroiClassic: LocatorObject = { locator: '.YoroiClassic', method: 'css' } \ No newline at end of file +export const yoroiClassic: LocatorObject = { locator: '.YoroiClassic', method: 'css' }; +export const serverErrorBanner = { locator: '.ServerErrorBanner_serverError', method: 'css' }; +export const maintenanceBody = { locator: '.Maintenance_body', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/newWalletPages.js b/packages/yoroi-extension/features/pages/newWalletPages.js index 473a61f47f..2be897e07a 100644 --- a/packages/yoroi-extension/features/pages/newWalletPages.js +++ b/packages/yoroi-extension/features/pages/newWalletPages.js @@ -31,4 +31,6 @@ export const trezorConfirmButton: LocatorObject = { locator: '.MuiButton-primary // Common elements export const walletNameInput: LocatorObject = { locator: '//input[@name="walletName"]', method: 'xpath' }; export const saveDialog: LocatorObject = { locator: '.SaveDialog', method: 'css' }; -export const saveButton: LocatorObject = { locator: '//button[@id="primaryButton"]', method: 'xpath' }; \ No newline at end of file +export const saveButton: LocatorObject = { locator: '//button[@id="primaryButton"]', method: 'xpath' }; +export const checkDialog: LocatorObject = { locator: '.CheckDialog_component', method: 'css' }; +export const sendConfirmationDialog: LocatorObject = { locator: '.HWSendConfirmationDialog_dialog', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/settingsPage.js b/packages/yoroi-extension/features/pages/settingsPage.js index 2ec9e1b36f..7c32bd10e9 100644 --- a/packages/yoroi-extension/features/pages/settingsPage.js +++ b/packages/yoroi-extension/features/pages/settingsPage.js @@ -2,14 +2,96 @@ import type { LocatorObject } from '../support/webdriver'; -export const settingsLayoutComponent: LocatorObject = { locator: '.SettingsLayout_component', method: 'css' }; +export const fullScreenMessage = { locator: '.FullscreenMessage_title', method: 'css' }; + +// Wallet tab + +export const walletNameInputSelector = { + locator: '.SettingsLayout_settingsPane .walletName input', + method: 'css', +}; +export const walletNameInput = { + locator: '.SettingsLayout_settingsPane .InlineEditingInput_component', + method: 'css', +}; +export const walletSettingsPane = { + locator: '.SettingsLayout_settingsPane', + method: 'css', +}; +export const removeWalletButton = { locator: '.removeWallet', method: 'css' }; +export const resyncWalletButton = { locator: '.resyncButton', method: 'css' }; +export const exportButton = { locator: '.exportWallet', method: 'css' }; +export const exportPublicKeyDialog = { locator: '.ExportPublicKeyDialog_component', method: 'css' }; + +// Level of complexity tab + +export const complexityLevelForm: LocatorObject = { + locator: '.ComplexityLevelForm_cardsWrapper', + method: 'css', +}; +export const complexitySelected: LocatorObject = { locator: '.currentLevel', method: 'css' }; + +// General tab + +export const languageSelector: LocatorObject = { + locator: '//div[starts-with(@id, "languageId")]', + method: 'xpath', +}; +export const settingsLayoutComponent: LocatorObject = { + locator: '.SettingsLayout_component', + method: 'css', +}; export const secondThemeSelected: LocatorObject = { - locator: '.ThemeSettingsBlock_themesWrapper button:nth-child(2).ThemeSettingsBlock_active', - method: 'css' - }; + locator: '.ThemeSettingsBlock_themesWrapper button:nth-child(2).ThemeSettingsBlock_active', + method: 'css', +}; + +// Change password dialog + +export const currentPasswordInput: LocatorObject = { + locator: '.changePasswordDialog .currentPassword input', + method: 'css', +}; +export const newPasswordInput: LocatorObject = { + locator: '.changePasswordDialog .newPassword input', + method: 'css', +}; +export const repeatPasswordInput: LocatorObject = { + locator: '.changePasswordDialog .repeatedPassword input', + method: 'css', +}; +export const confirmButton = { locator: '.confirmButton', method: 'css' }; +export const changePasswordDialog = { locator: '.changePasswordDialog', method: 'css' }; +export const walletPasswordHelperText = { + locator: '//p[starts-with(@id, "walletPassword--") and contains(@id, "-helper-text")]', + method: 'xpath', +}; +export const helperText = { locator: '.MuiFormHelperText-root', method: 'css' }; +export const changePasswordDialogError = { + locator: '.ChangeWalletPasswordDialog_error', + method: 'css', +}; + +// Support/Logs Tab - export const complexityLevelForm: LocatorObject = { locator: '.ComplexityLevelForm_cardsWrapper', method: 'css' }; - export const complexitySelected: LocatorObject = { locator: '.currentLevel', method: 'css' }; - export const languageSelector: LocatorObject = { locator: '//div[starts-with(@id, "languageId")]', method: 'xpath' }; +export const faqTitle = { + locator: "//h1[contains(text(), 'Frequently asked questions')]", + method: 'xpath', +}; +export const reportingAProblemTitle = { + locator: "//h1[contains(text(), 'Reporting a problem')]", + method: 'xpath', +}; +export const logsTitle = { locator: "//h1[contains(text(), 'Logs')]", method: 'xpath' }; +// Blockchain Tab +export const explorerSettingsDropdown = { locator: '.ExplorerSettings_component', method: 'css' }; +export const cardanoPaymentsURLTitle = { + locator: "//h2[contains(text(), 'Cardano Payment URLs')]", + method: 'xpath', +}; +export const currencyConversionText = { + locator: "//h2[contains(text(), 'Currency Conversion')]", + method: 'xpath', +}; diff --git a/packages/yoroi-extension/features/pages/sidebarPage.js b/packages/yoroi-extension/features/pages/sidebarPage.js index b3b7151aa2..97e9554fa8 100644 --- a/packages/yoroi-extension/features/pages/sidebarPage.js +++ b/packages/yoroi-extension/features/pages/sidebarPage.js @@ -6,4 +6,8 @@ export const stakingButton = { locator: 'sidebar.staking', method: 'id' }; export const assetsButton = { locator: 'sidebar.assets', method: 'id' }; export const votingButton = { locator: 'sidebar.voting', method: 'id' }; export const settingsButton = { locator: 'sidebar.settings', method: 'id' }; -export const faqButton = { locator: '.SidebarRevamp_faq', method: 'css' }; \ No newline at end of file +export const faqButton = { locator: '.SidebarRevamp_faq', method: 'css' }; + +// Classic version elements + +export const walletButtonClassic = { locator: `//div[@class='Sidebar_categories']//button[1]`, method: 'xpath' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/walletPage.js b/packages/yoroi-extension/features/pages/walletPage.js index e1a0f7bb8c..5d74816da8 100644 --- a/packages/yoroi-extension/features/pages/walletPage.js +++ b/packages/yoroi-extension/features/pages/walletPage.js @@ -8,4 +8,11 @@ export const claimTransferTab = { locator: '.claimTransfer', method: 'css' }; export const walletNameText = { locator: '.NavPlate_name', method: 'css' }; export const activeNavTab = { locator: '.WalletNavButton_active', method: 'css' }; -export const dashboardTab = { locator: '.stakeDashboard ', method: 'css' }; \ No newline at end of file +export const dashboardTab = { locator: '.stakeDashboard ', method: 'css' }; +export const transactionsTab = { locator: `//span[contains(text(), "Transactions")]`, method: 'xpath' }; + +export const navDetailsAmount = { locator: '.NavWalletDetails_amount', method: 'css' }; +export const navDetailsHideButton = { locator: '.NavWalletDetails_toggleButton', method: 'css' }; +export const navDetailsWalletDropdown = { locator: '.NavDropdown_toggle', method: 'css' }; +export const navDetailsBuyButton = { locator: '.NavDropdownContent_buyButton', method: 'css' }; +export const buyDialogAddress = { locator: '.BuySellDialog_address', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/walletReceivePage.js b/packages/yoroi-extension/features/pages/walletReceivePage.js index 387bb35067..e87c696336 100644 --- a/packages/yoroi-extension/features/pages/walletReceivePage.js +++ b/packages/yoroi-extension/features/pages/walletReceivePage.js @@ -21,3 +21,6 @@ export const generateAddressButton = { locator: '.generateAddressButton', method export const addressBookTab = { locator: `.addressBook`, method: 'css' }; export const rewardAddressTab = { locator: `.reward`, method: 'css' }; export const yourWalletAddrHeader = { locator: '.StandardHeader_copyableHash', method: 'css' }; +export const verifyAddressButton = { locator: '.WalletReceive_verifyIcon', method: 'css' }; + +export const verifyAddressHWButton = { locator: '.VerifyAddressDialog_component .primary', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js new file mode 100644 index 0000000000..8cbbeb2f7a --- /dev/null +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -0,0 +1,6 @@ +// @flow + +export const receiverInput = { locator: "input[name='receiver']", method: 'css' }; +export const amountInput = { locator: "input[name='amount']", method: 'css' }; +export const addMemoButton = { locator: '.addMemoButton', method: 'css' }; +export const memoContentInput = { locator: "input[name='memoContent']", method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletTransactionsPage.js b/packages/yoroi-extension/features/pages/walletTransactionsPage.js index 83ac150032..73041831de 100644 --- a/packages/yoroi-extension/features/pages/walletTransactionsPage.js +++ b/packages/yoroi-extension/features/pages/walletTransactionsPage.js @@ -1,3 +1,5 @@ // @flow -export const walletSummaryBox = { locator: 'walletSummary_box', method: 'id' } \ No newline at end of file +export const walletSummaryBox = { locator: 'walletSummary_box', method: 'id' }; +export const walletSummaryComponent = { locator: "//div[@class='WalletSummary_component']", method: 'xpath' }; +export const copyToClipboardButton = { locator: '.CopyableAddress_copyIconBig', method: 'css' }; diff --git a/packages/yoroi-extension/features/step_definitions/hardware-steps.js b/packages/yoroi-extension/features/step_definitions/hardware-steps.js index abbc5fc42f..bcedc3298b 100644 --- a/packages/yoroi-extension/features/step_definitions/hardware-steps.js +++ b/packages/yoroi-extension/features/step_definitions/hardware-steps.js @@ -1,95 +1,111 @@ // @flow -import { When, Then, } from 'cucumber'; +import { When, Then } from 'cucumber'; import { testWallets } from '../mock-chain/TestWallets'; -import { truncateAddress, } from '../../app/utils/formatters'; +import { truncateAddress } from '../../app/utils/formatters'; import { addressField, derivationField, verifyButton } from '../pages/verifyAddressPage'; import { expect } from 'chai'; -import { byronEraButton, pickUpCurrencyDialog, pickUpCurrencyDialogCardano, shelleyEraButton } from '../pages/newWalletPages'; +import { + byronEraButton, + checkDialog, + connectHwButton, + hwOptionsDialog, + ledgerWalletButton, + pickUpCurrencyDialog, + pickUpCurrencyDialogCardano, + saveDialog, + sendConfirmationDialog, + shelleyEraButton, + trezorWalletButton, +} from '../pages/newWalletPages'; +import { errorBlockComponent, primaryButton } from '../pages/commonDialogPage'; +import { walletNameInput } from '../pages/restoreWalletPage'; +import { verifyAddressButton, verifyAddressHWButton } from '../pages/walletReceivePage'; When(/^I select a Byron-era Ledger device$/, async function () { - await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); + await this.click(connectHwButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); + await this.waitForElement(hwOptionsDialog); - await this.click({ locator: '.WalletConnectHWOptionDialog_connectLedger', method: 'css' }); + await this.click(ledgerWalletButton); await this.click(byronEraButton); }); When(/^I select a Shelley-era Ledger device$/, async function () { - await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); + await this.click(connectHwButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); + await this.waitForElement(hwOptionsDialog); - await this.click({ locator: '.WalletConnectHWOptionDialog_connectLedger', method: 'css' }); + await this.click(ledgerWalletButton); await this.click(shelleyEraButton); }); When(/^I restore the Ledger device$/, async function () { - await this.waitForElement({ locator: '.CheckDialog_component', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); + await this.waitForElement(checkDialog); + await this.click(primaryButton); + await this.click(primaryButton); // between these is where the tab & iframe gets opened - await this.waitForElement({ locator: '.SaveDialog', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); + await this.waitForElement(saveDialog); + await this.click(primaryButton); }); - When(/^I select a Byron-era Trezor device$/, async function () { - await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); + await this.click(connectHwButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); + await this.waitForElement(hwOptionsDialog); - await this.click({ locator: '.WalletConnectHWOptionDialog_connectTrezor', method: 'css' }); + await this.click(trezorWalletButton); await this.click(byronEraButton); }); When(/^I select a Shelley-era Trezor device$/, async function () { - await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); + await this.click(connectHwButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); + await this.waitForElement(hwOptionsDialog); - await this.click({ locator: '.WalletConnectHWOptionDialog_connectTrezor', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }); + await this.click(trezorWalletButton); + await this.click(shelleyEraButton); }); When(/^I restore the Trezor device$/, async function () { - await this.waitForElement({ locator: '.CheckDialog_component', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); + await this.waitForElement(checkDialog); + await this.click(primaryButton); + await this.click(primaryButton); // between these is where the tab & iframe gets opened - await this.waitForElement({ locator: '.SaveDialog', method: 'css' }); - await this.input({ locator: "input[name='walletName']", method: 'css' }, testWallets['trezor-wallet'].name); - await this.click({ locator: '.primary', method: 'css' }); + await this.waitForElement(saveDialog); + await this.input(walletNameInput, testWallets['trezor-wallet'].name); + await this.click(primaryButton); }); - When(/^I see the hardware send money confirmation dialog$/, async function () { - await this.waitForElement({ locator: '.HWSendConfirmationDialog_dialog', method: 'css' }); + await this.waitForElement(sendConfirmationDialog); }); When(/^I click on the verify address button$/, async function () { - const allVerifyAddressButtons = await this.findElements({ locator: '.WalletReceive_verifyIcon', method: 'css' }); + const allVerifyAddressButtons = await this.findElements(verifyAddressButton); await allVerifyAddressButtons[0].click(); }); When(/^I see the verification address "([^"]*)"$/, async function (expectAddress) { await this.waitForElement(addressField); const actualAddressStr = await this.getText(addressField); - expect(actualAddressStr).to.equal(truncateAddress(expectAddress), `The actual verification address is different from expected.`); + expect(actualAddressStr).to.equal( + truncateAddress(expectAddress), + `The actual verification address is different from expected.` + ); }); When(/^I see the derivation path "([^"]*)"$/, async function (path) { @@ -97,15 +113,15 @@ When(/^I see the derivation path "([^"]*)"$/, async function (path) { }); Then(/^I verify the address on my ledger device$/, async function () { - await this.click({ locator: '.VerifyAddressDialog_component .primary', method: 'css' }); - await this.waitDisable({ locator: '.VerifyAddressDialog_component .primary', method: 'css' }); // disable when communicating with device - await this.waitEnable({ locator: '.VerifyAddressDialog_component .primary', method: 'css' }); // enable after it's done + await this.click(verifyAddressHWButton); + await this.waitDisable(verifyAddressHWButton); // disable when communicating with device + await this.waitEnable(verifyAddressHWButton); // enable after it's done await this.driver.sleep(1000); - await this.waitForElementNotPresent({ locator: '.ErrorBlock_component', method: 'css' }); + await this.waitForElementNotPresent(errorBlockComponent); }); Then(/^I verify the address on my trezor device$/, async function () { await this.click(verifyButton); // we should have this disable while the action is processing, but we don't show a spinner on this - await this.waitForElementNotPresent({ locator: '.ErrorBlock_component', method: 'css' }); + await this.waitForElementNotPresent(errorBlockComponent); }); diff --git a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js index 8478bf7ae9..96e66f830d 100644 --- a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js +++ b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js @@ -3,11 +3,12 @@ import { Given, When, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import { expect } from 'chai'; +import { termsOfUseComponent } from '../pages/basicSetupPage'; const TERMS_OF_USE_FORM = '.TermsOfUseForm_component'; Given(/^I am on the "Terms of use" screen$/, async function () { - await this.waitForElement({ locator: TERMS_OF_USE_FORM, method: 'css' }); + await this.waitForElement(termsOfUseComponent); }); When(/^I click on "I agree with the terms of use" checkbox$/, async function () { @@ -23,10 +24,7 @@ When(/^I submit the "Terms of use" form$/, async function () { }); Then(/^I should not see the "Terms of use" screen anymore$/, async function () { - await this.waitForElementNotPresent({ - locator: TERMS_OF_USE_FORM, - method: 'css' - }); + await this.waitForElementNotPresent(termsOfUseComponent); }); Then(/^I should have "Terms of use" accepted$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/main-ui-steps.js b/packages/yoroi-extension/features/step_definitions/main-ui-steps.js index c10d86de3c..c6a479b9e8 100644 --- a/packages/yoroi-extension/features/step_definitions/main-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/main-ui-steps.js @@ -4,72 +4,92 @@ import { When, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import { hiddenAmount } from '../../app/utils/strings'; -import { truncateAddress, } from '../../app/utils/formatters'; +import { truncateAddress } from '../../app/utils/formatters'; +import { + buyDialogAddress, + navDetailsAmount, + navDetailsBuyButton, + navDetailsHideButton, + navDetailsWalletDropdown, + transactionsTab, +} from '../pages/walletPage'; +import { amountInput, receiverInput } from '../pages/walletSendPage'; +import { walletButtonClassic } from '../pages/sidebarPage'; +import { copyToClipboardButton, walletSummaryComponent } from '../pages/walletTransactionsPage'; +import { maintenanceBody, serverErrorBanner } from '../pages/mainWindowPage'; Then(/^I should see the balance number "([^"]*)"$/, async function (number) { - await this.waitUntilText({ locator: '.NavWalletDetails_amount', method: 'css' }, number); + await this.waitUntilText(navDetailsAmount, number); }); Then(/^I should see send transaction screen$/, async function () { - await this.waitForElement({ locator: "input[name='receiver']", method: 'css' }); - await this.waitForElement({ locator: "input[name='amount']", method: 'css' }); + await this.waitForElement(receiverInput); + await this.waitForElement(amountInput); }); Then(/^I go to the transaction history screen$/, async function () { - await this.click({ locator: `//span[contains(text(), "Transactions")]`, method: 'xpath' }); + await this.click(transactionsTab); }); When(/^I go to the main screen$/, async function () { - await this.click({ locator: `//div[@class='Sidebar_categories']//button[1]`, method: 'xpath' }); + await this.click(walletButtonClassic); }); Then(/^I should see the transactions screen$/, async function () { - await this.waitForElement({ locator: "//div[@class='WalletSummary_component']", method: 'xpath' }); + await this.waitForElement(walletSummaryComponent); }); Then(/^I click on "copy to clipboard" button$/, async function () { - await this.click({ locator: '.CopyableAddress_copyIconBig', method: 'css' }); + await this.click(copyToClipboardButton); }); Then(/^I should see "copied" tooltip message:$/, async function (data) { const notification = data.hashes()[0]; const notificationMessage = await this.intl(notification.message); - const messageParentElement = await this.driver.findElement(By.xpath('//div[contains(@role, "tooltip")]')); - const message = await messageParentElement.findElement(By.xpath(`//span[contains(text(), "${notificationMessage}")]`)); + const messageParentElement = await this.driver.findElement( + By.xpath('//div[contains(@role, "tooltip")]') + ); + const message = await messageParentElement.findElement( + By.xpath(`//span[contains(text(), "${notificationMessage}")]`) + ); expect(await message.isDisplayed()).to.be.true; }); Then(/^I see transactions buttons are disabled$/, async function () { - const disabledButtons = await this.driver.findElement(By.xpath("//button[contains(@class, 'confirmButton') and contains(@class, 'disabled')]")); + const disabledButtons = await this.driver.findElement( + By.xpath("//button[contains(@class, 'confirmButton') and contains(@class, 'disabled')]") + ); const pageUrl = await this.driver.getCurrentUrl(); disabledButtons.click(); expect(pageUrl).to.be.equal(await this.driver.getCurrentUrl()); }); Then(/^I should see the networkError banner$/, async function () { - await this.waitForElement({ locator: '.ServerErrorBanner_serverError', method: 'css' }); + await this.waitForElement(serverErrorBanner); }); Then(/^I should see the serverError banner$/, async function () { - await this.waitForElement({ locator: '.ServerErrorBanner_serverError', method: 'css' }); + await this.waitForElement(serverErrorBanner); }); Then(/^I should see the app maintenance page$/, async function () { - await this.waitForElement({ locator: '.Maintenance_body', method: 'css' }); + await this.waitForElement(maintenanceBody); }); Then(/^I click on hide balance button$/, async function () { - await this.click({ locator: '.NavWalletDetails_toggleButton', method: 'css' }); + await this.click(navDetailsHideButton); }); Then(/^I should see my balance hidden$/, async function () { - await this.waitForElement({ locator: '.NavWalletDetails_amount', method: 'css' }); - await this.waitUntilContainsText({ locator: '.NavWalletDetails_amount', method: 'css' }, hiddenAmount); + await this.waitForElement(navDetailsAmount); + await this.waitUntilContainsText(navDetailsAmount, hiddenAmount); }); Then(/^I switch to "([^"]*)" from the dropdown$/, async function (walletName) { - await this.click({ locator: '.NavDropdown_toggle', method: 'css' }); - const wallets = await this.driver.findElements(By.xpath("//button[contains(@class, 'NavDropdownRow_head')]")); + await this.click(navDetailsWalletDropdown); + const wallets = await this.driver.findElements( + By.xpath("//button[contains(@class, 'NavDropdownRow_head')]") + ); for (const wallet of wallets) { const nameElem = await wallet.findElement(By.css('.NavPlate_name')); const foundName = await nameElem.getText(); @@ -82,10 +102,10 @@ Then(/^I switch to "([^"]*)" from the dropdown$/, async function (walletName) { }); Then(/^I select buy-sell from the dropdown$/, async function () { - await this.click({ locator: '.NavDropdown_toggle', method: 'css' }); - await this.click({ locator: '.NavDropdownContent_buyButton', method: 'css' }); + await this.click(navDetailsWalletDropdown); + await this.click(navDetailsBuyButton); }); Then(/^I should see the pre-filled address "([^"]*)"$/, async function (address) { - await this.waitUntilContainsText({ locator: '.BuySellDialog_address', method: 'css' }, truncateAddress(address)); + await this.waitUntilContainsText(buyDialogAddress, truncateAddress(address)); }); diff --git a/packages/yoroi-extension/features/step_definitions/memo-steps.js b/packages/yoroi-extension/features/step_definitions/memo-steps.js index aa81507ba8..9327fdc819 100644 --- a/packages/yoroi-extension/features/step_definitions/memo-steps.js +++ b/packages/yoroi-extension/features/step_definitions/memo-steps.js @@ -4,12 +4,14 @@ import { Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import chai from 'chai'; import { MAX_MEMO_SIZE } from '../../app/config/externalStorageConfig'; +import { primaryButton } from '../pages/commonDialogPage'; +import { addMemoButton, memoContentInput } from '../pages/walletSendPage'; Then(/^I add a memo that says "([^"]*)"$/, async function (memo) { - await this.click({ locator: '.addMemoButton', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); - await this.input({ locator: "input[name='memoContent']", method: 'css' }, memo); - await this.click({ locator: '.primary', method: 'css' }); + await this.click(addMemoButton); + await this.click(primaryButton); + await this.input(memoContentInput, memo); + await this.click(primaryButton); }); Then(/^The memo content says "([^"]*)"$/, async function (memo) { @@ -21,15 +23,15 @@ Then(/^The memo content says "([^"]*)"$/, async function (memo) { Then(/^I edit the memo to say "([^"]*)"$/, async function (memo) { await this.click({ locator: '.editMemoButton', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); - await this.clearInputUpdatingForm({ locator: "input[name='memoContent']", method: 'css' }, MAX_MEMO_SIZE); - await this.input({ locator: "input[name='memoContent']", method: 'css' }, memo); - await this.click({ locator: '.primary', method: 'css' }); + await this.click(primaryButton); + await this.clearInputUpdatingForm(memoContentInput, MAX_MEMO_SIZE); + await this.input(memoContentInput, memo); + await this.click(primaryButton); }); Then(/^I delete the memo$/, async function () { await this.click({ locator: '.editMemoButton', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); + await this.click(primaryButton); let memoComponent = await this.driver.findElement(By.css('.MemoDialogCommon_component')); const deleteButton = await memoComponent.findElement(By.xpath('//button[@aria-label="delete memo"]')); await deleteButton.click(); @@ -39,14 +41,14 @@ Then(/^I delete the memo$/, async function () { }); Then(/^There is no memo for the transaction$/, async function () { - await this.waitForElement({ locator: '.addMemoButton', method: 'css' }); + await this.waitForElement(addMemoButton); }); Then(/^I add a transaction memo that says "([^"]*)"$/, async function (memo) { await this.driver.sleep(500); - await this.click({ locator: '.addMemoButton', method: 'css' }); + await this.click(addMemoButton); await this.driver.sleep(500); - await this.click({ locator: '.MemoDialogCommon_component .primary', method: 'css' }); + await this.click(primaryButton); await this.driver.sleep(500); - await this.input({ locator: "input[name='memo']", method: 'css' }, memo); + await this.input(memoContentInput, memo); }); diff --git a/packages/yoroi-extension/features/step_definitions/select-language-steps.js b/packages/yoroi-extension/features/step_definitions/select-language-steps.js index 7e9a73e191..36f257800c 100644 --- a/packages/yoroi-extension/features/step_definitions/select-language-steps.js +++ b/packages/yoroi-extension/features/step_definitions/select-language-steps.js @@ -3,7 +3,7 @@ import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; import languageSelection, { clickContinue } from '../support/helpers/language-selection-helpers'; -import { languageSelectionForm } from '../pages/basicSetupPage'; +import { japaneseLaguageSelection, languageSelectionForm } from '../pages/basicSetupPage'; const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; @@ -20,7 +20,7 @@ When(/^I open language selection dropdown$/, async function () { }); When(/^I select Japanese language$/, async function () { - return this.click({ locator: '//span[contains(text(), "日本語")]', method: 'xpath' }); + return this.click(japaneseLaguageSelection); }); When(/^I submit the language selection form$/, async function () { @@ -28,10 +28,7 @@ When(/^I submit the language selection form$/, async function () { }); Then(/^I should not see the language selection screen anymore$/, async function () { - await this.waitForElementNotPresent({ - locator: LANGUAGE_SELECTION_FORM, - method: 'css' - }); + await this.waitForElementNotPresent(languageSelectionForm); }); Then(/^I should have Japanese language set$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js index ff54f9924c..b18e47b66d 100644 --- a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js @@ -3,12 +3,35 @@ import { When, Given, Then } from 'cucumber'; import i18n from '../support/helpers/i18n-helpers'; import { By, Key } from 'selenium-webdriver'; -import { truncateLongName, } from '../../app/utils/formatters'; +import { truncateLongName } from '../../app/utils/formatters'; import { expect } from 'chai'; import { checkErrorByTranslationId } from './common-steps'; import { walletNameText } from '../pages/walletPage'; - -const walletNameInputSelector = '.SettingsLayout_settingsPane .walletName input'; +import { + changePasswordDialog, + changePasswordDialogError, + confirmButton, + currentPasswordInput, + explorerSettingsDropdown, + helperText, + newPasswordInput, + repeatPasswordInput, + walletNameInput, + walletNameInputSelector, + walletPasswordHelperText, + walletSettingsPane, + faqTitle, + logsTitle, + reportingAProblemTitle, + cardanoPaymentsURLTitle, + currencyConversionText, + removeWalletButton, + resyncWalletButton, + exportButton, + exportPublicKeyDialog, + fullScreenMessage, +} from '../pages/settingsPage'; +import { dialogTitle } from '../pages/commonDialogPage'; Given(/^I should see the "([^"]*)" wallet password dialog$/, async function (dialogType) { const selector = '.' + dialogType + 'PasswordDialog'; @@ -16,7 +39,7 @@ Given(/^I should see the "([^"]*)" wallet password dialog$/, async function (dia }); When(/^I click on "name" input field$/, async function () { - await this.click({ locator: '.SettingsLayout_settingsPane .InlineEditingInput_component', method: 'css' }); + await this.click(walletNameInput); }); When(/^I enter new wallet name:$/, async function (table) { @@ -26,17 +49,17 @@ When(/^I enter new wallet name:$/, async function (table) { * This makes our InlineEditingInput become disabled causing the clear and sendKeys to fail * https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/214 * We instead repeatedly delete characters until we've deleted the whole name - */ + */ // can't programmatically get the wallet name due to the issue above // so assume max length const maxNameLength = 40; for (let i = 0; i < maxNameLength; i++) { // Chrome and Firefox select the text field starting at the left / right respectively - await this.input({ locator: walletNameInputSelector, method: 'css' }, Key.BACK_SPACE); // Firefox - await this.input({ locator: walletNameInputSelector, method: 'css' }, Key.DELETE); // Chrome + await this.input(walletNameInputSelector, Key.BACK_SPACE); // Firefox + await this.input(walletNameInputSelector, Key.DELETE); // Chrome } - await this.input({ locator: walletNameInputSelector, method: 'css' }, fields.name); + await this.input(walletNameInputSelector, fields.name); }); Then(/^I should see the "Terms of use" screen$/, async function () { @@ -44,7 +67,7 @@ Then(/^I should see the "Terms of use" screen$/, async function () { }); When(/^I click outside "name" input field$/, async function () { - await this.click({ locator: '.SettingsLayout_settingsPane', method: 'css' }); + await this.click(walletSettingsPane); }); When(/^I click on the "([^"]*)" password label$/, async function (label) { @@ -54,29 +77,29 @@ When(/^I click on the "([^"]*)" password label$/, async function (label) { When(/^I change wallet password:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: '.changePasswordDialog .currentPassword input', method: 'css' }, fields.currentPassword); - await this.input({ locator: '.changePasswordDialog .newPassword input', method: 'css' }, fields.password); - await this.input({ locator: '.changePasswordDialog .repeatedPassword input', method: 'css' }, fields.repeatedPassword); + await this.input(currentPasswordInput, fields.currentPassword); + await this.input(newPasswordInput, fields.password); + await this.input(repeatPasswordInput, fields.repeatedPassword); }); When(/^I clear the current wallet password ([^"]*)$/, async function (password) { - await this.clearInputUpdatingForm({ locator: '.changePasswordDialog .currentPassword input', method: 'css' }, password.length); + await this.clearInputUpdatingForm(currentPasswordInput, password.length); }); When(/^I clear the current wallet repeat password ([^"]*)$/, async function (repeatPassword) { - await this.clearInputUpdatingForm({ locator: '.changePasswordDialog .repeatedPassword input', method: 'css' }, repeatPassword.length); + await this.clearInputUpdatingForm(repeatPasswordInput, repeatPassword.length); }); When(/^I submit the wallet password dialog$/, async function () { - await this.click({ locator: '.confirmButton', method: 'css' }); + await this.click(confirmButton); }); When(/^I click the next button$/, async function () { - await this.click({ locator: '.confirmButton', method: 'css' }); + await this.click(confirmButton); }); Then(/^I should not see the change password dialog anymore$/, async function () { - await this.waitForElementNotPresent({ locator: '.changePasswordDialog', method: 'css' }); + await this.waitForElementNotPresent(changePasswordDialog); }); Then(/^I should see new wallet name "([^"]*)"$/, async function (walletName) { @@ -85,88 +108,74 @@ Then(/^I should see new wallet name "([^"]*)"$/, async function (walletName) { Then(/^I should see the following error messages:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '//p[starts-with(@id, "walletPassword--") and contains(@id, "-helper-text")]', method: 'xpath' }, - error); + await checkErrorByTranslationId(this, walletPasswordHelperText, error); }); Then(/^I should see "Doesn't match" error message:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '.MuiFormHelperText-root', method: 'css' }, - error); + await checkErrorByTranslationId(this, helperText, error); }); Then(/^I should see the following submit error messages:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '.ChangeWalletPasswordDialog_error', method: 'css' }, - error); + await checkErrorByTranslationId(this, changePasswordDialogError, error); }); Then(/^I should stay in the change password dialog$/, async function () { - const changePasswordMessage = await i18n.formatMessage(this.driver, - { id: 'wallet.settings.changePassword.dialog.title.changePassword' }); - await this.waitUntilText({ locator: '.dialog__title', method: 'css' }, changePasswordMessage.toUpperCase(), 2000); + const changePasswordMessage = await i18n.formatMessage(this.driver, { + id: 'wallet.settings.changePassword.dialog.title.changePassword', + }); + await this.waitUntilText(dialogTitle, changePasswordMessage.toUpperCase(), 2000); }); Then(/^I should see support screen$/, async function () { - await this.waitForElement({ locator: "//h1[contains(text(), 'Frequently asked questions')]", method: 'xpath' }); - await this.waitForElement({ locator: "//h1[contains(text(), 'Reporting a problem')]", method: 'xpath' }); - await this.waitForElement({ locator: "//h1[contains(text(), 'Logs')]", method: 'xpath' }); + await this.waitForElement(faqTitle); + await this.waitForElement(reportingAProblemTitle); + await this.waitForElement(logsTitle); }); Then(/^I should see blockchain screen$/, async function () { - await this.waitForElement({ locator: '.ExplorerSettings_component', method: 'css' }); - await this.waitForElement({ locator: "//h2[contains(text(), 'Cardano Payment URLs')]", method: 'xpath' }); - await this.waitForElement({ locator: "//h2[contains(text(), 'Currency Conversion')]", method: 'xpath' }); + await this.waitForElement(explorerSettingsDropdown); + await this.waitForElement(cardanoPaymentsURLTitle); + await this.waitForElement(currencyConversionText); }); When(/^I click on remove wallet$/, async function () { - await this.click({ locator: '.removeWallet', method: 'css' }); + await this.click(removeWalletButton); }); When(/^I click on resync wallet$/, async function () { - await this.click({ locator: '.resyncButton', method: 'css' }); + await this.click(resyncWalletButton); }); When(/^I click on export wallet$/, async function () { - await this.click({ locator: '.exportWallet', method: 'css' }); + await this.click(exportButton); }); Then(/^I should see the wallet export for key "([^"]*)"$/, async function (expectedKey) { - await this.waitForElement({ locator: '.ExportPublicKeyDialog_component', method: 'css' }); + await this.waitForElement(exportPublicKeyDialog); const publicKeyForm = await this.driver.findElement(By.css('.CodeBlock_component')); const publicKey = await publicKeyForm.getText(); expect(publicKey).to.equal(expectedKey); }); Then(/^I click on the checkbox$/, async function () { - const warningCheckboxElement = await this.driver.findElement(By.css('.DangerousActionDialog_checkbox')); + const warningCheckboxElement = await this.driver.findElement( + By.css('.DangerousActionDialog_checkbox') + ); const checkbox = await warningCheckboxElement.findElement(By.xpath('//input[@type="checkbox"]')); await checkbox.click(); }); Then(/^I should see a no wallet message$/, async function () { - const noWalletMessage = await i18n.formatMessage( - this.driver, - { id: 'wallet.nowallet.title' } - ); - await this.waitUntilText({ locator: '.FullscreenMessage_title', method: 'css' }, noWalletMessage); + const noWalletMessage = await i18n.formatMessage(this.driver, { id: 'wallet.nowallet.title' }); + await this.waitUntilText(fullScreenMessage, noWalletMessage); }); Then(/^I sleep for ([^"]*)$/, async function (ms) { await this.driver.sleep(Number.parseInt(ms, 10)); }); -Then(/^I should see "Incorrect wallet password." error message$/, async function(){ - const errorSelector = '.ChangeWalletPasswordDialog_error'; - await this.waitUntilText( - { locator: errorSelector, method: 'css' }, - 'Incorrect wallet password.', - 15000 - ); -}); \ No newline at end of file +Then(/^I should see "Incorrect wallet password." error message$/, async function () { + await this.waitUntilText(changePasswordDialogError, 'Incorrect wallet password.', 15000); +}); diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index a63f21e1ed..3f828c1036 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -12,6 +12,7 @@ import { defaultAssets, } from '../../app/api/ada/lib/storage/database/prepackaged/networks'; import { walletSummaryBox } from '../pages/walletTransactionsPage'; +import { amountInput, receiverInput } from '../pages/walletSendPage'; Given(/^I have a wallet with funds$/, async function () { const amountWithCurrency = await this.driver.findElements( @@ -29,13 +30,13 @@ When(/^I go to the send transaction screen$/, async function () { When(/^I fill the form:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: "input[name='receiver']", method: 'css' }, fields.address); - await this.input({ locator: "input[name='amount']", method: 'css' }, fields.amount); + await this.input(receiverInput, fields.address); + await this.input(amountInput, fields.amount); }); When(/^I fill the address of the form:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: "input[name='receiver']", method: 'css' }, fields.address); + await this.input(receiverInput, fields.address); }); Given(/^The expected transaction is "([^"]*)"$/, base64Tx => { @@ -63,7 +64,7 @@ When(/^I see CONFIRM TRANSACTION Pop up:$/, async function (table) { }); When(/^I clear the receiver$/, async function () { - await this.clearInput({ locator: "input[name='receiver']", method: 'css' }); + await this.clearInput(receiverInput); }); When(/^I clear the wallet password ([^"]*)$/, async function (password) { @@ -71,7 +72,7 @@ When(/^I clear the wallet password ([^"]*)$/, async function (password) { }); When(/^I fill the receiver as "([^"]*)"$/, async function (receiver) { - await this.input({ locator: "input[name='receiver']", method: 'css' }, receiver); + await this.input(receiverInput, receiver); }); When(/^The transaction fees are "([^"]*)"$/, async function (fee) { diff --git a/packages/yoroi-extension/features/step_definitions/trezor-steps.js b/packages/yoroi-extension/features/step_definitions/trezor-steps.js index 876c9cd2f2..82d32d4b35 100644 --- a/packages/yoroi-extension/features/step_definitions/trezor-steps.js +++ b/packages/yoroi-extension/features/step_definitions/trezor-steps.js @@ -10,6 +10,7 @@ import { import { TrezorEmulatorController } from '../support/trezorEmulatorController'; import { expect } from 'chai'; import { verifyButton } from '../pages/verifyAddressPage'; +import { errorBlockComponent } from '../pages/commonDialogPage'; export async function switchToTrezorAndAllow(customWorld: any) { // wait for a new tab @@ -85,5 +86,5 @@ Then(/^I verify the address on the trezor emulator$/, async function () { } await this.windowManager.waitForClosingAndSwitchTo(trezorConnectTabName, extensionTabName); // we should have this disable while the action is processing, but we don't show a spinner on this - await this.waitForElementNotPresent({ locator: '.ErrorBlock_component', method: 'css' }); + await this.waitForElementNotPresent(errorBlockComponent); }); \ No newline at end of file diff --git a/packages/yoroi-extension/features/step_definitions/uri-steps.js b/packages/yoroi-extension/features/step_definitions/uri-steps.js index 13c50c36a9..d01c9f927f 100644 --- a/packages/yoroi-extension/features/step_definitions/uri-steps.js +++ b/packages/yoroi-extension/features/step_definitions/uri-steps.js @@ -4,6 +4,7 @@ import { When, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import { truncateAddress, } from '../../app/utils/formatters'; +import { amountInput } from '../pages/walletSendPage'; When(/^I click on "generate payment URL" button$/, async function () { await this.click({ locator: '.WalletReceive_generateURIIcon', method: 'css' }); @@ -11,7 +12,7 @@ When(/^I click on "generate payment URL" button$/, async function () { }); Then(/^I generate a URI for ([0-9]+) ADA$/, async function (amount) { - await this.input({ locator: "input[name='amount']", method: 'css' }, amount); + await this.input(amountInput, amount); await this.click({ locator: '.URIGenerateDialog_component .MuiButton-primary', method: 'css' }); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js index 5c9234b8cc..b4b98ab4d6 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js @@ -11,6 +11,7 @@ import { pickUpCurrencyDialog } from '../pages/newWalletPages'; import { continueButton } from '../pages/basicSetupPage'; +import { dialogTitle } from '../pages/commonDialogPage'; When(/^I click the create button$/, async function () { await this.click(createWalletButton); @@ -117,7 +118,7 @@ Then(/^I see All selected words are cleared$/, async function () { Then(/^I should stay in the create wallet dialog$/, async function () { const createMessage = await i18n.formatMessage(this.driver, { id: 'wallet.create.dialog.title' }); - await this.waitUntilText({ locator: '.dialog__title', method: 'css' }, createMessage.toUpperCase(), 2000); + await this.waitUntilText(dialogTitle, createMessage.toUpperCase(), 2000); }); Then( diff --git a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js index b5bbbd0dfe..0b7ebd054b 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js @@ -5,6 +5,7 @@ import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import { truncateAddress, } from '../../app/utils/formatters'; import { enterRecoveryPhrase } from '../support/helpers/helpers'; +import { primaryButton } from '../pages/commonDialogPage'; // ========== Paper wallet ========== @@ -17,7 +18,7 @@ Then(/^I select 2 addresses$/, async function () { }); Then(/^I click the create paper wallet button$/, async function () { - await this.click({ locator: '.primary', method: 'css' }); + await this.click(primaryButton); }); const fakeAddresses = [ diff --git a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js index 687be13eab..e2bea7a8b6 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js @@ -8,6 +8,7 @@ import { checkErrorByTranslationId, getPlates } from './common-steps'; import { enterRecoveryPhrase } from '../support/helpers/helpers'; import { cleanRecoverInput, + confirmButton, errorInvalidRecoveryPhrase, getWords, paperPasswordInput, @@ -17,6 +18,7 @@ import { } from '../pages/restoreWalletPage'; import { masterKeyInput } from '../pages/walletClaimTransferPage'; import { pickUpCurrencyDialog, pickUpCurrencyDialogCardano, restoreNormalWallet, shelleyEraButton, walletRestoreDialog, walletRestoreOptionDialog } from '../pages/newWalletPages'; +import { dialogTitle } from '../pages/commonDialogPage'; When(/^I click the restore button for ([^"]*)$/, async function (currency) { await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); @@ -149,7 +151,7 @@ Then(/^I should stay in the restore wallet dialog$/, async function () { const restoreMessage = await i18n.formatMessage(this.driver, { id: 'wallet.restore.dialog.title.label', }); - await this.waitUntilText({ locator: '.dialog__title', method: 'css' }, restoreMessage.toUpperCase(), 2000); + await this.waitUntilText(dialogTitle, restoreMessage.toUpperCase(), 2000); }); Then(/^I delete recovery phrase by clicking "x" signs$/, async function () { @@ -201,5 +203,5 @@ Then(/^I should see the wallet already exist window$/, async function () { }); When(/^I click the Open wallet button$/, async function () { - await this.click({ locator: '.confirmButton', method: 'css' }); + await this.click(confirmButton); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-steps.js index a9a1b9e5b7..fc21b063ff 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-steps.js @@ -2,14 +2,15 @@ import { When, Then } from 'cucumber'; import { truncateLongName } from '../../app/utils/formatters'; +import { walletNameInput } from '../pages/restoreWalletPage'; import { walletNameText } from '../pages/walletPage'; When(/^I enter the name "([^"]*)"$/, async function (walletName) { - await this.input({ locator: "input[name='walletName']", method: 'css' }, walletName); + await this.input(walletNameInput, walletName); }); When(/^I clear the name "([^"]*)"$/, async function (walletName) { - await this.clearInputUpdatingForm({ locator: "input[name='walletName']", method: 'css' }, walletName.length); + await this.clearInputUpdatingForm(walletNameInput, walletName.length); }); When(/^I navigate to wallet sidebar category$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js b/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js index 3e17acce60..829898338e 100644 --- a/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js +++ b/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js @@ -14,6 +14,7 @@ import { } from '../support/helpers/transfer-helpers'; import { claimTransferTab } from '../pages/walletPage'; import { byronButton } from '../pages/walletClaimTransferPage'; +import { primaryButton } from '../pages/commonDialogPage'; async function confirmAttentionScreen(customWorld: Object){ // Attention screen @@ -71,7 +72,7 @@ Then(/^I see the transfer transaction$/, async function () { await this.waitForElement({ locator: '.TransferSummaryPage_body', method: 'css' }); }); Then(/^I accept the prompt$/, async function () { - await this.click({ locator: '.primary', method: 'css' }); + await this.click(primaryButton); }); Then(/^I select the trezor option$/, async function () { await this.click({ locator: '.fromTrezor_connectTrezor', method: 'css' }); From 837ecbeb5942fe8afc8b1a3d2860afb9b13ebc88 Mon Sep 17 00:00:00 2001 From: Rafael Castro Date: Tue, 30 Aug 2022 14:32:32 -0300 Subject: [PATCH 062/199] redesign connect page --- .../components/connect/ConnectPage.js | 62 ++++++++++++------- .../components/connect/ConnectPage.scss | 29 +++++---- .../components/connect/WalletCard.js | 15 +++-- .../components/connect/WalletCard.scss | 19 +++++- .../components/layout/Layout.scss | 5 +- .../containers/ConnectContainer.js | 9 +++ .../containers/ConnectContainer.stories.js | 12 +++- 7 files changed, 102 insertions(+), 49 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js index e5ed79cd3e..d8e98c05ce 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js @@ -30,6 +30,8 @@ import vjf from 'mobx-react-form/lib/validators/VJF'; import { WrongPassphraseError } from '../../../api/ada/lib/cardanoCrypto/cryptoErrors' import { ReactComponent as NoWalletImage } from '../../assets/images/no-websites-connected.inline.svg' import { ReactComponent as NoDappIcon } from '../../../assets/images/dapp-connector/no-dapp.inline.svg'; +import { ReactComponent as IconEyeOpen } from '../../../assets/images/my-wallets/icon_eye_open.inline.svg'; +import { ReactComponent as IconEyeClosed } from '../../../assets/images/my-wallets/icon_eye_closed.inline.svg' const messages = defineMessages({ subtitle: { @@ -96,6 +98,7 @@ type Props = {| +getTokenInfo: ($ReadOnly>) => $ReadOnly, +network: string, +shouldHideBalance: boolean, + +onUpdateHideBalance: void => Promise, |}; @observer @@ -186,6 +189,7 @@ class ConnectPage extends Component { network, shouldHideBalance, isAppAuth, + onUpdateHideBalance, } = this.props; const isNightly = environment.isNightly(); const componentClasses = classNames([styles.component, isNightly && styles.isNightly]); @@ -291,7 +295,7 @@ class ConnectPage extends Component { passwordForm ) : ( <> - + {isError ?
    {error}
    : null} {isLoading ? (
    @@ -299,14 +303,23 @@ class ConnectPage extends Component {
    ) : hasWallets ? ( - - {intl.formatMessage(messages.yourWallets)} - +
    + + {intl.formatMessage(messages.yourWallets)} + + +
    +
      {publicDerivers.map(item => (
    • { ) : null} - - {intl.formatMessage(messages.connectWalletNoHardwareSupported)} - )} + { + hasWallets && !isAppAuth ? +
      +

      + {intl.formatMessage(messages.connectWalletNoHardwareSupported)} +

      - {hasWallets && isAppAuth ? ( -
      -
      -

      {intl.formatMessage(messages.connectInfo)}

      -

      {intl.formatMessage(connectorMessages.messageReadOnly)}

      -
      -
      - ) : null} +

      + {intl.formatMessage(messages.connectInfo)} +

      +

      + {intl.formatMessage(connectorMessages.messageReadOnly)} +

      + +
      : null }
    ); } diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss index 20608955d2..fecb2f3ecb 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss @@ -1,9 +1,11 @@ .component { - height: calc(100vh - 92px); - overflow: auto; + display: flex; + flex-direction: column; + height: calc(100vh - 52px); + overflow: auto; &.isNightly { height: calc(100vh - 138px); - } + } } .connectWrapper { padding-top: 47px; @@ -30,8 +32,16 @@ font-size: 18px; font-weight: 500; } +} + +.titleWallet { + display: flex; + & * + * { + padding-left: 10px; + } } + .noWallets { display: flex; align-items: center; @@ -81,21 +91,14 @@ } .bottom { - padding-left: 32px; - padding-right: 32px; + border-top: 1px solid #dce0e9; + padding: 15px 32px; .infoText { color: var(--yoroi-palette-gray-600); font-size: 12px; - margin-bottom: 32px; line-height: 20px; } - & .wrapperBtn { - display: flex; - button + button { - margin-left: 22px; - } - } } .errorMessage { color: var(--yoroi-comp-input-error); @@ -118,4 +121,4 @@ padding-left: 0px; } } -} \ No newline at end of file +} diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.js b/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.js index 09d59ad2fb..103f87ad36 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.js +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.js @@ -1,6 +1,7 @@ // @flow import { Component } from 'react'; import type { Node } from 'react'; +import { Chip } from '@mui/material'; import styles from './WalletCard.scss'; import WalletAccountIcon from '../../../components/topbar/WalletAccountIcon'; import type { WalletChecksum } from '@emurgo/cip4-js'; @@ -27,8 +28,8 @@ function constructPlate(
    , ]; @@ -50,11 +51,17 @@ export default class WalletCard extends Component { : assetNameFromIdentifier(defaultEntry.identifier); const { shouldHideBalance } = this.props; + const checksum = this.props.publicDeriver.checksum?.TextPart; return (
    -
    {iconComponent}
    -
    {this.props.publicDeriver.name}
    +
    +
    {iconComponent}
    +
    +
    {this.props.publicDeriver.name}
    + +
    +

    {shouldHideBalance ? hiddenAmount : shiftedAmount.toString()}{' '} {ticker} diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.scss b/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.scss index a01a83e368..f6703b15fc 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.scss +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.scss @@ -1,21 +1,36 @@ .card { display: flex; align-items: center; + justify-content: space-between; width: 100%; letter-spacing: 0; line-height: 22px; + & .avatar { margin-right: 13px; - width: 32px; - height: 32px; border-radius: 50%; overflow: hidden; object-fit: cover; + height: 50px; + width: 50px; } + + & .wrapper { + display: flex; + align-items: center; + } + + & .nameWrapper { + display: flex; + flex-flow: column; + align-items: flex-start; + } + & .name { flex: 1; text-align: left; } + & .balance { font-weight: 500; } diff --git a/packages/yoroi-extension/app/ergo-connector/components/layout/Layout.scss b/packages/yoroi-extension/app/ergo-connector/components/layout/Layout.scss index 5f164256f5..c6a33269a8 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/layout/Layout.scss +++ b/packages/yoroi-extension/app/ergo-connector/components/layout/Layout.scss @@ -1,15 +1,14 @@ .layout { min-height: 100vh; - padding-bottom: 40px; border-radius: 2px; - background-color: #f0f3f5; + background-color: #FFFFFF; box-shadow: 0 2px 5px 3px rgba(0, 0, 0, 0.06); font-weight: 400; } .header { height: 52px; - background: linear-gradient(45deg, #244abf 0%, #4760ff 100%); + background: var(--yoroi-sidebar-background); display: flex; align-items: center; padding-right: 32px; diff --git a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js index f46bb99eb9..cc7cdb9cfd 100644 --- a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js +++ b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js @@ -157,6 +157,10 @@ export default class ConnectContainer extends Component< this.setState({ isAppAuth: false }) } + updateHideBalance: void => Promise = async () => { + await this.generated.actions.connector.updateHideBalance.trigger(); + }; + render(): Node { const responseMessage = this.generated.stores.connector.connectingMessage; const wallets = this.generated.stores.connector.filteredWallets; @@ -180,6 +184,7 @@ export default class ConnectContainer extends Component< network={network} getTokenInfo={genLookupOrFail(this.generated.stores.tokenInfoStore.tokenInfo)} shouldHideBalance={this.generated.stores.profile.shouldHideBalance} + onUpdateHideBalance={this.updateHideBalance} /> ); } @@ -199,6 +204,9 @@ export default class ConnectContainer extends Component< getConnectorWhitelist: {| trigger: (params: void) => Promise, |}, + updateHideBalance: {| + trigger: (params: void) => Promise, + |}, updateConnectorWhitelist: {| trigger: ({| whitelist: Array, @@ -254,6 +262,7 @@ export default class ConnectContainer extends Component< closeWindow: { trigger: actions.connector.closeWindow.trigger }, getConnectorWhitelist: { trigger: actions.connector.getConnectorWhitelist.trigger }, updateConnectorWhitelist: { trigger: actions.connector.updateConnectorWhitelist.trigger }, + updateHideBalance: { trigger: actions.profile.updateHideBalance.trigger } }, }, }); diff --git a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.stories.js b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.stories.js index 3fe4c9e832..12da2ab4c1 100644 --- a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.stories.js +++ b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.stories.js @@ -64,15 +64,20 @@ export const Generic = (): Node => { generated={{ stores: { profile: { - shouldHideBalance: false + shouldHideBalance: false, }, connector: { - connectingMessage: undefined, + connectingMessage: { + imgBase64Url: '', + protocol: 'cardano', + tabId: 0, + url: 'localhost' + }, filteredWallets: wallets, errorWallets, loadingWallets: walletsState, currentConnectorWhitelist: [], - protocol: '', + protocol: '' }, tokenInfoStore: { tokenInfo: mockFromDefaults(defaultAssets), @@ -85,6 +90,7 @@ export const Generic = (): Node => { updateConnectorWhitelist: { trigger: async (req) => action('updateConnectorWhitelist')(req) }, refreshWallets: { trigger: async (req) => action('refreshWallets')(req) }, closeWindow: { trigger: action('closeWindow') }, + updateHideBalance: { trigger: async (req) => action('refreshWallets')(req) } }, }, }} From 81dc2848d8d1b4eb27a469700ceb1622cbc6745c Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Tue, 30 Aug 2022 15:48:32 -0300 Subject: [PATCH 063/199] Moved more locators to corresponding pages --- .../features/pages/commonDialogPage.js | 7 +- .../features/pages/confirmTransactionPage.js | 11 +- .../features/pages/daedalusTransferPage.js | 17 +- .../features/pages/errorPage.js | 6 +- .../features/pages/mainWindowPage.js | 9 +- .../features/pages/newWalletPages.js | 172 ++++++++++++-- .../features/pages/restoreWalletPage.js | 30 +-- .../features/pages/settingsPage.js | 53 +++-- .../features/pages/sidebarPage.js | 22 +- .../features/pages/trezorConnectPage.js | 2 +- .../features/pages/uriPromptPage.js | 2 +- .../features/pages/verifyAddressPage.js | 10 +- .../features/pages/walletClaimTransferPage.js | 63 ++++- .../features/pages/walletDashboardPage.js | 5 +- .../features/pages/walletDelegationPage.js | 30 +++ .../features/pages/walletPage.js | 54 +++-- .../features/pages/walletReceivePage.js | 68 +++++- .../features/pages/walletSendPage.js | 77 ++++++- .../features/pages/walletTransactionsPage.js | 38 +++- .../features/pages/walletVotingPage.js | 48 ++++ .../features/pages/walletsListPage.js | 8 +- .../features/step_definitions/common-steps.js | 11 +- .../step_definitions/transactions-steps.js | 90 +++++--- .../features/step_definitions/trezor-steps.js | 106 ++++----- .../step_definitions/tx-history-steps.js | 215 +++++++++--------- .../features/step_definitions/uri-steps.js | 29 +-- .../features/step_definitions/voting-steps.js | 66 ++++-- .../step_definitions/wallet-creation-steps.js | 79 ++++--- .../wallet-delegation-steps.js | 18 +- .../step_definitions/wallet-paper-steps.js | 29 +-- .../wallet-restoration-steps.js | 44 ++-- .../features/step_definitions/wallet-steps.js | 10 +- .../step_definitions/yoroi-transfer-steps.js | 103 +++++---- 33 files changed, 1062 insertions(+), 470 deletions(-) create mode 100644 packages/yoroi-extension/features/pages/walletDelegationPage.js create mode 100644 packages/yoroi-extension/features/pages/walletVotingPage.js diff --git a/packages/yoroi-extension/features/pages/commonDialogPage.js b/packages/yoroi-extension/features/pages/commonDialogPage.js index 242f03173f..4c1c6b8fa0 100644 --- a/packages/yoroi-extension/features/pages/commonDialogPage.js +++ b/packages/yoroi-extension/features/pages/commonDialogPage.js @@ -3,5 +3,8 @@ import type { LocatorObject } from '../support/webdriver'; export const primaryButton: LocatorObject = { locator: '.primary', method: 'css' }; -export const errorBlockComponent: LocatorObject = { locator: '.ErrorBlock_component', method: 'css' }; -export const dialogTitle = { locator: '.dialog__title', method: 'css' }; +export const errorBlockComponent: LocatorObject = { + locator: '.ErrorBlock_component', + method: 'css', +}; +export const dialogTitle: LocatorObject = { locator: '.dialog__title', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/confirmTransactionPage.js b/packages/yoroi-extension/features/pages/confirmTransactionPage.js index 30284d7c3c..47eefd528e 100644 --- a/packages/yoroi-extension/features/pages/confirmTransactionPage.js +++ b/packages/yoroi-extension/features/pages/confirmTransactionPage.js @@ -1,5 +1,10 @@ // @flow -export const feeField = { locator: '.TransferSummaryPage_fees', method: 'css' }; -export const amountField = { locator: '.TransferSummaryPage_amount', method: 'css' }; -export const totalAmountField = { locator: '.TransferSummaryPage_totalAmount', method: 'css' }; \ No newline at end of file +import type { LocatorObject } from '../support/webdriver'; + +export const feeField: LocatorObject = { locator: '.TransferSummaryPage_fees', method: 'css' }; +export const amountField: LocatorObject = { locator: '.TransferSummaryPage_amount', method: 'css' }; +export const totalAmountField: LocatorObject = { + locator: '.TransferSummaryPage_totalAmount', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/daedalusTransferPage.js b/packages/yoroi-extension/features/pages/daedalusTransferPage.js index f04a4cfc80..ec44f968ea 100644 --- a/packages/yoroi-extension/features/pages/daedalusTransferPage.js +++ b/packages/yoroi-extension/features/pages/daedalusTransferPage.js @@ -2,7 +2,16 @@ import type { LocatorObject } from '../support/webdriver'; -export const nextButton: LocatorObject = { locator: "//button[contains(@label, 'Next')]", method: 'xpath' }; -export const backButton: LocatorObject = { locator: "//button[contains(@label, 'Back')]", method: 'xpath' }; -export const formFieldOverridesClassicError: LocatorObject = { locator: '.FormFieldOverridesClassic_error', method: 'css' }; -export const transferButton: LocatorObject = { locator: '.transferButton', method: 'css' }; \ No newline at end of file +export const nextButton: LocatorObject = { + locator: "//button[contains(@label, 'Next')]", + method: 'xpath', +}; +export const backButton: LocatorObject = { + locator: "//button[contains(@label, 'Back')]", + method: 'xpath', +}; +export const formFieldOverridesClassicError: LocatorObject = { + locator: '.FormFieldOverridesClassic_error', + method: 'css', +}; +export const transferButton: LocatorObject = { locator: '.transferButton', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/errorPage.js b/packages/yoroi-extension/features/pages/errorPage.js index 8ea7a5905a..e565d484a2 100644 --- a/packages/yoroi-extension/features/pages/errorPage.js +++ b/packages/yoroi-extension/features/pages/errorPage.js @@ -1,4 +1,6 @@ // @flow -export const errorPageTitle = { locator: '.ErrorPage_title', method: 'css' }; -export const errorMessage = { locator: '.ErrorPage_error', method: 'css' }; \ No newline at end of file +import type { LocatorObject } from '../support/webdriver'; + +export const errorPageTitle: LocatorObject = { locator: '.ErrorPage_title', method: 'css' }; +export const errorMessage: LocatorObject = { locator: '.ErrorPage_error', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/mainWindowPage.js b/packages/yoroi-extension/features/pages/mainWindowPage.js index c2e14ce0e2..7b58e5161a 100644 --- a/packages/yoroi-extension/features/pages/mainWindowPage.js +++ b/packages/yoroi-extension/features/pages/mainWindowPage.js @@ -3,5 +3,10 @@ import type { LocatorObject } from '../support/webdriver'; export const yoroiClassic: LocatorObject = { locator: '.YoroiClassic', method: 'css' }; -export const serverErrorBanner = { locator: '.ServerErrorBanner_serverError', method: 'css' }; -export const maintenanceBody = { locator: '.Maintenance_body', method: 'css' }; \ No newline at end of file +export const serverErrorBanner: LocatorObject = { + locator: '.ServerErrorBanner_serverError', + method: 'css', +}; +export const maintenanceBody: LocatorObject = { locator: '.Maintenance_body', method: 'css' }; + +export const myWalletsPage: LocatorObject = { locator: '.MyWallets_page', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/newWalletPages.js b/packages/yoroi-extension/features/pages/newWalletPages.js index 2be897e07a..d77ff91ffc 100644 --- a/packages/yoroi-extension/features/pages/newWalletPages.js +++ b/packages/yoroi-extension/features/pages/newWalletPages.js @@ -3,34 +3,170 @@ import type { LocatorObject } from '../support/webdriver'; export const connectHwButton: LocatorObject = { locator: '.WalletAdd_btnConnectHW', method: 'css' }; -export const createWalletButton: LocatorObject = { locator: '.WalletAdd_btnCreateWallet', method: 'css' }; -export const restoreWalletButton: LocatorObject = { locator: '.WalletAdd_btnRestoreWallet', method: 'css' }; +export const createWalletButton: LocatorObject = { + locator: '.WalletAdd_btnCreateWallet', + method: 'css', +}; +export const walletAddRestoreWalletButton: LocatorObject = { + locator: '.WalletAdd_btnRestoreWallet', + method: 'css', +}; // Currency options dialog -export const pickUpCurrencyDialog: LocatorObject = { locator: '.PickCurrencyOptionDialog', method: 'css' }; -export const pickUpCurrencyDialogErgo: LocatorObject = { locator: '.PickCurrencyOptionDialog_ergo', method: 'css' }; -export const pickUpCurrencyDialogCardano: LocatorObject = { locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }; -export const walletRestoreOptionDialog = { locator: '.WalletRestoreOptionDialog', method: 'css' }; -export const restoreNormalWallet = { locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }; -export const walletRestoreDialog = { locator: '.WalletRestoreDialog', method: 'css' }; +export const pickUpCurrencyDialog: LocatorObject = { + locator: '.PickCurrencyOptionDialog', + method: 'css', +}; +export const pickUpCurrencyDialogErgo: LocatorObject = { + locator: '.PickCurrencyOptionDialog_ergo', + method: 'css', +}; +export const pickUpCurrencyDialogCardano: LocatorObject = { + locator: '.PickCurrencyOptionDialog_cardano', + method: 'css', +}; +export const walletRestoreOptionDialog: LocatorObject = { + locator: '.WalletRestoreOptionDialog', + method: 'css', +}; +export const restoreNormalWallet: LocatorObject = { + locator: '.WalletRestoreOptionDialog_restoreNormalWallet', + method: 'css', +}; +export const restore24WordWallet: LocatorObject = { + locator: '.WalletRestoreOptionDialog_normal24WordWallet', + method: 'css', +}; +export const walletRestoreDialog: LocatorObject = { + locator: '.WalletRestoreDialog', + method: 'css', +}; export const getCurrencyButton = (currency: string): LocatorObject => { - return { locator: `.PickCurrencyOptionDialog_${currency}`, method: 'css' }; + return { locator: `.PickCurrencyOptionDialog_${currency}`, method: 'css' }; }; // HW options dialog -export const hwOptionsDialog: LocatorObject = { locator: '.WalletConnectHWOptionDialog', method: 'css' }; -export const ledgerWalletButton: LocatorObject = { locator: '.WalletConnectHWOptionDialog_connectLedger', method: 'css' }; -export const trezorWalletButton: LocatorObject = { locator: '.WalletConnectHWOptionDialog_connectTrezor', method: 'css' }; +export const hwOptionsDialog: LocatorObject = { + locator: '.WalletConnectHWOptionDialog', + method: 'css', +}; +export const ledgerWalletButton: LocatorObject = { + locator: '.WalletConnectHWOptionDialog_connectLedger', + method: 'css', +}; +export const trezorWalletButton: LocatorObject = { + locator: '.WalletConnectHWOptionDialog_connectTrezor', + method: 'css', +}; // Era options dialog export const eraOptionsDialog: LocatorObject = { locator: '.WalletEraOptionDialog', method: 'css' }; -export const shelleyEraButton: LocatorObject = { locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }; -export const byronEraButton: LocatorObject = { locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }; +export const shelleyEraButton: LocatorObject = { + locator: '.WalletEraOptionDialog_bgShelleyMainnet', + method: 'css', +}; +export const byronEraButton: LocatorObject = { + locator: '.WalletEraOptionDialog_bgByronMainnet', + method: 'css', +}; // Trezor connect dialog export const trezorConnectDialog: LocatorObject = { locator: '.CheckDialog', method: 'css' }; -export const trezorWalletName: LocatorObject = { locator: '//input[@name="walletName"]', method: 'xpath' }; +export const trezorWalletName: LocatorObject = { + locator: '//input[@name="walletName"]', + method: 'xpath', +}; export const trezorConfirmButton: LocatorObject = { locator: '.MuiButton-primary', method: 'css' }; +// Create wallet dialog +export const createOptionDialog: LocatorObject = { + locator: '.WalletCreateOptionDialog', + method: 'css', +}; +export const createPaperWalletButton: LocatorObject = { + locator: '.WalletCreateOptionDialog_restorePaperWallet', + method: 'css', +}; +export const createWalletPasswordInput: LocatorObject = { + locator: '.WalletCreateDialog .walletPassword input', + method: 'css', +}; +export const createWalletRepeatPasswordInput: LocatorObject = { + locator: '.WalletCreateDialog .repeatedPassword input', + method: 'css', +}; +export const createPersonalWalletButton: LocatorObject = { + locator: '.WalletCreateDialog .primary', + method: 'css', +}; +export const createWalletPasswordHelperText: LocatorObject = { + locator: '//p[starts-with(@id, "walletPassword") and contains(@id, "-helper-text")]', + method: 'xpath', +}; +export const walletRecoveryPhraseMnemonicComponent: LocatorObject = { + locator: '.WalletRecoveryPhraseMnemonic_component', + method: 'css', +}; +export const createWalletNameError: LocatorObject = { + locator: '.walletName .MuiFormHelperText-root', + method: 'css', +}; +export const createWalletPasswordError: LocatorObject = { + locator: '.FormFieldOverridesClassic_error', + method: 'css', +}; +export const securityWarning: LocatorObject = { + locator: '.MuiFormControlLabel-root', + method: 'css', +}; + +// Recovery Phrase dialog +export const recoveryPhraseButton: LocatorObject = { + locator: '.WalletRecoveryPhraseDisplayDialog .primary', + method: 'css', +}; +export const recoveryPhraseConfirmButton: LocatorObject = { + locator: '//button[text()="Confirm"]', + method: 'xpath', +}; +export const clearButton: LocatorObject = { + locator: "//button[contains(text(), 'Clear')]", + method: 'xpath', +}; + +// Paper Wallet dialog +export const paperWalletDialogSelect: LocatorObject = { + locator: '.WalletPaperDialog_component .MuiSelect-select', + method: 'css', +}; +export const restorePaperWalletButton: LocatorObject = { + locator: '.WalletRestoreOptionDialog_restorePaperWallet', + method: 'css', +}; +export const restoreDialogButton: LocatorObject = { + locator: '.WalletRestoreDialog .primary', + method: 'css', +}; +export const recoveryPhraseDeleteIcon = { + locator: `(//span[contains(text(), '×')])[1]`, + method: 'xpath', +}; +export const recoveryPhraseError: LocatorObject = { + locator: '//p[starts-with(@id, "recoveryPhrase--")]', + method: 'xpath', +}; // Common elements -export const walletNameInput: LocatorObject = { locator: '//input[@name="walletName"]', method: 'xpath' }; +export const walletNameInput: LocatorObject = { + locator: '//input[@name="walletName"]', + method: 'xpath', +}; export const saveDialog: LocatorObject = { locator: '.SaveDialog', method: 'css' }; -export const saveButton: LocatorObject = { locator: '//button[@id="primaryButton"]', method: 'xpath' }; +export const saveButton: LocatorObject = { + locator: '//button[@id="primaryButton"]', + method: 'xpath', +}; export const checkDialog: LocatorObject = { locator: '.CheckDialog_component', method: 'css' }; -export const sendConfirmationDialog: LocatorObject = { locator: '.HWSendConfirmationDialog_dialog', method: 'css' }; \ No newline at end of file +export const sendConfirmationDialog: LocatorObject = { + locator: '.HWSendConfirmationDialog_dialog', + method: 'css', +}; +export const walletAlreadyExistsComponent: LocatorObject = { + locator: '.WalletAlreadyExistDialog_component', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/restoreWalletPage.js b/packages/yoroi-extension/features/pages/restoreWalletPage.js index b2a43a4fb7..26fc60ccde 100644 --- a/packages/yoroi-extension/features/pages/restoreWalletPage.js +++ b/packages/yoroi-extension/features/pages/restoreWalletPage.js @@ -2,33 +2,33 @@ import type { LocatorObject } from '../support/webdriver'; -export const errorInvalidRecoveryPhrase = { +export const errorInvalidRecoveryPhrase: LocatorObject = { locator: '//p[contains(@class, "-error") and contains(@id, "recoveryPhrase")]', - method: 'xpath' + method: 'xpath', }; -export const recoveryPhraseField = { +export const recoveryPhraseField: LocatorObject = { locator: '//input[starts-with(@id, "downshift-") and contains(@id, "-input")]', - method: 'xpath' + method: 'xpath', }; -export const proceedRecoveryButton = { +export const proceedRecoveryButton: LocatorObject = { locator: 'primaryButton', - method: 'id' + method: 'id', }; -export const cleanRecoverInput = { +export const cleanRecoverInput: LocatorObject = { locator: '.AutocompleteOverridesClassic_autocompleteWrapper input', - method: 'css' + method: 'css', }; export const getWords = (word: string): LocatorObject => { - return { locator: `//span[contains(text(), '${word}')]`, method: 'xpath' } + return { locator: `//span[contains(text(), '${word}')]`, method: 'xpath' }; }; -export const walletNameInput = { locator: "input[name='walletName']", method: 'css' }; -export const restoreWalletButton = { locator: '.WalletRestoreDialog .primary', method: 'css' } -export const walletPasswordInput = { locator: "input[name='walletPassword']", method: 'css' }; -export const repeatPasswordInput = { locator: "input[name='repeatPassword']", method: 'css' }; -export const paperPasswordInput = { locator: "input[name='paperPassword']", method: 'css' }; -export const confirmButton = { locator: '.confirmButton', method: 'css' }; \ No newline at end of file +export const walletNameInput: LocatorObject = { locator: "input[name='walletName']", method: 'css' }; +export const restoreWalletButton: LocatorObject = { locator: '.WalletRestoreDialog .primary', method: 'css' }; +export const walletPasswordInput: LocatorObject = { locator: "input[name='walletPassword']", method: 'css' }; +export const repeatPasswordInput: LocatorObject = { locator: "input[name='repeatPassword']", method: 'css' }; +export const paperPasswordInput: LocatorObject = { locator: "input[name='paperPassword']", method: 'css' }; +export const confirmButton: LocatorObject = { locator: '.confirmButton', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/settingsPage.js b/packages/yoroi-extension/features/pages/settingsPage.js index 7c32bd10e9..f4d4051fd2 100644 --- a/packages/yoroi-extension/features/pages/settingsPage.js +++ b/packages/yoroi-extension/features/pages/settingsPage.js @@ -2,26 +2,32 @@ import type { LocatorObject } from '../support/webdriver'; -export const fullScreenMessage = { locator: '.FullscreenMessage_title', method: 'css' }; +export const fullScreenMessage: LocatorObject = { + locator: '.FullscreenMessage_title', + method: 'css', +}; // Wallet tab -export const walletNameInputSelector = { +export const walletNameInputSelector: LocatorObject = { locator: '.SettingsLayout_settingsPane .walletName input', method: 'css', }; -export const walletNameInput = { +export const walletNameInput: LocatorObject = { locator: '.SettingsLayout_settingsPane .InlineEditingInput_component', method: 'css', }; -export const walletSettingsPane = { +export const walletSettingsPane: LocatorObject = { locator: '.SettingsLayout_settingsPane', method: 'css', }; -export const removeWalletButton = { locator: '.removeWallet', method: 'css' }; -export const resyncWalletButton = { locator: '.resyncButton', method: 'css' }; -export const exportButton = { locator: '.exportWallet', method: 'css' }; -export const exportPublicKeyDialog = { locator: '.ExportPublicKeyDialog_component', method: 'css' }; +export const removeWalletButton: LocatorObject = { locator: '.removeWallet', method: 'css' }; +export const resyncWalletButton: LocatorObject = { locator: '.resyncButton', method: 'css' }; +export const exportButton: LocatorObject = { locator: '.exportWallet', method: 'css' }; +export const exportPublicKeyDialog: LocatorObject = { + locator: '.ExportPublicKeyDialog_component', + method: 'css', +}; // Level of complexity tab @@ -60,38 +66,47 @@ export const repeatPasswordInput: LocatorObject = { locator: '.changePasswordDialog .repeatedPassword input', method: 'css', }; -export const confirmButton = { locator: '.confirmButton', method: 'css' }; -export const changePasswordDialog = { locator: '.changePasswordDialog', method: 'css' }; -export const walletPasswordHelperText = { +export const confirmButton: LocatorObject = { locator: '.confirmButton', method: 'css' }; +export const changePasswordDialog: LocatorObject = { + locator: '.changePasswordDialog', + method: 'css', +}; +export const walletPasswordHelperText: LocatorObject = { locator: '//p[starts-with(@id, "walletPassword--") and contains(@id, "-helper-text")]', method: 'xpath', }; -export const helperText = { locator: '.MuiFormHelperText-root', method: 'css' }; -export const changePasswordDialogError = { +export const helperText: LocatorObject = { locator: '.MuiFormHelperText-root', method: 'css' }; +export const changePasswordDialogError: LocatorObject = { locator: '.ChangeWalletPasswordDialog_error', method: 'css', }; // Support/Logs Tab -export const faqTitle = { +export const faqTitle: LocatorObject = { locator: "//h1[contains(text(), 'Frequently asked questions')]", method: 'xpath', }; -export const reportingAProblemTitle = { +export const reportingAProblemTitle: LocatorObject = { locator: "//h1[contains(text(), 'Reporting a problem')]", method: 'xpath', }; -export const logsTitle = { locator: "//h1[contains(text(), 'Logs')]", method: 'xpath' }; +export const logsTitle: LocatorObject = { + locator: "//h1[contains(text(), 'Logs')]", + method: 'xpath', +}; // Blockchain Tab -export const explorerSettingsDropdown = { locator: '.ExplorerSettings_component', method: 'css' }; -export const cardanoPaymentsURLTitle = { +export const explorerSettingsDropdown: LocatorObject = { + locator: '.ExplorerSettings_component', + method: 'css', +}; +export const cardanoPaymentsURLTitle: LocatorObject = { locator: "//h2[contains(text(), 'Cardano Payment URLs')]", method: 'xpath', }; -export const currencyConversionText = { +export const currencyConversionText: LocatorObject = { locator: "//h2[contains(text(), 'Currency Conversion')]", method: 'xpath', }; diff --git a/packages/yoroi-extension/features/pages/sidebarPage.js b/packages/yoroi-extension/features/pages/sidebarPage.js index 97e9554fa8..f8b8260680 100644 --- a/packages/yoroi-extension/features/pages/sidebarPage.js +++ b/packages/yoroi-extension/features/pages/sidebarPage.js @@ -1,13 +1,21 @@ // @flow // Revamped sidebar's elements -export const walletButton = { locator: 'settings.menu.wallet.link.label', method: 'id' }; -export const stakingButton = { locator: 'sidebar.staking', method: 'id' }; -export const assetsButton = { locator: 'sidebar.assets', method: 'id' }; -export const votingButton = { locator: 'sidebar.voting', method: 'id' }; -export const settingsButton = { locator: 'sidebar.settings', method: 'id' }; -export const faqButton = { locator: '.SidebarRevamp_faq', method: 'css' }; +import type { LocatorObject } from '../support/webdriver'; + +export const walletButton: LocatorObject = { + locator: 'settings.menu.wallet.link.label', + method: 'id', +}; +export const stakingButton: LocatorObject = { locator: 'sidebar.staking', method: 'id' }; +export const assetsButton: LocatorObject = { locator: 'sidebar.assets', method: 'id' }; +export const votingButton: LocatorObject = { locator: 'sidebar.voting', method: 'id' }; +export const settingsButton: LocatorObject = { locator: 'sidebar.settings', method: 'id' }; +export const faqButton: LocatorObject = { locator: '.SidebarRevamp_faq', method: 'css' }; // Classic version elements -export const walletButtonClassic = { locator: `//div[@class='Sidebar_categories']//button[1]`, method: 'xpath' }; \ No newline at end of file +export const walletButtonClassic: LocatorObject = { + locator: `//div[@class='Sidebar_categories']//button[1]`, + method: 'xpath', +}; diff --git a/packages/yoroi-extension/features/pages/trezorConnectPage.js b/packages/yoroi-extension/features/pages/trezorConnectPage.js index c925a7dd91..9eafe619b5 100644 --- a/packages/yoroi-extension/features/pages/trezorConnectPage.js +++ b/packages/yoroi-extension/features/pages/trezorConnectPage.js @@ -4,4 +4,4 @@ import type { LocatorObject } from '../support/webdriver'; export const dontAskAgainCheckbox: LocatorObject = { locator: '.custom-checkbox', method: 'css' }; export const confirmUsingTrezorButton: LocatorObject = { locator: '.confirm', method: 'css' }; -export const exportTrezorButton: LocatorObject = { locator: '.confirm', method: 'css' }; \ No newline at end of file +export const exportTrezorButton: LocatorObject = { locator: '.confirm', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/uriPromptPage.js b/packages/yoroi-extension/features/pages/uriPromptPage.js index 88de4cdbe8..ce2672b882 100644 --- a/packages/yoroi-extension/features/pages/uriPromptPage.js +++ b/packages/yoroi-extension/features/pages/uriPromptPage.js @@ -5,4 +5,4 @@ import type { LocatorObject } from '../support/webdriver'; export const uriPromptForm: LocatorObject = { locator: '.UriPromptForm_component', method: 'css' }; export const allowButton: LocatorObject = { locator: '.allowButton', method: 'css' }; export const uriAcceptComponent: LocatorObject = { locator: '.UriAccept_component', method: 'css' }; -export const finishButton: LocatorObject = { locator: '.finishButton', method: 'css' }; \ No newline at end of file +export const finishButton: LocatorObject = { locator: '.finishButton', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/verifyAddressPage.js b/packages/yoroi-extension/features/pages/verifyAddressPage.js index 947e8e14c2..26098679d3 100644 --- a/packages/yoroi-extension/features/pages/verifyAddressPage.js +++ b/packages/yoroi-extension/features/pages/verifyAddressPage.js @@ -2,6 +2,12 @@ import type { LocatorObject } from '../support/webdriver'; -export const verifyButton: LocatorObject = { locator: '.VerifyAddressDialog .primary', method: 'css' }; +export const verifyButton: LocatorObject = { + locator: '.VerifyAddressDialog .primary', + method: 'css', +}; export const addressField: LocatorObject = { locator: '.verificationAddress', method: 'css' }; -export const derivationField: LocatorObject = { locator: '.VerifyAddressDialog_derivation', method: 'css' }; \ No newline at end of file +export const derivationField: LocatorObject = { + locator: '.VerifyAddressDialog_derivation', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/walletClaimTransferPage.js b/packages/yoroi-extension/features/pages/walletClaimTransferPage.js index 938d2c1388..de1f3eaa13 100644 --- a/packages/yoroi-extension/features/pages/walletClaimTransferPage.js +++ b/packages/yoroi-extension/features/pages/walletClaimTransferPage.js @@ -1,6 +1,61 @@ // @flow -export const byronButton = { locator: '.TransferCards_byronEra', method: 'css' }; -export const daedalusMasterKeyButton = { locator: '.fromDaedalusMasterKey_masterKey', method: 'css' }; -export const twelveWordOption = { locator: '.fromDaedalusWallet12Word_legacyDaedalus', method: 'css' }; -export const masterKeyInput = { locator: 'input[name="masterKey"]', method: 'css' }; \ No newline at end of file +import type { LocatorObject } from '../support/webdriver'; + +export const byronButton: LocatorObject = { locator: '.TransferCards_byronEra', method: 'css' }; +export const daedalusMasterKeyButton: LocatorObject = { + locator: '.fromDaedalusMasterKey_masterKey', + method: 'css', +}; +export const twelveWordOption: LocatorObject = { + locator: '.fromDaedalusWallet12Word_legacyDaedalus', + method: 'css', +}; +export const masterKeyInput: LocatorObject = { locator: 'input[name="masterKey"]', method: 'css' }; +export const cancelTransferButton: LocatorObject = { locator: '.cancelTransferButton', method: 'css' }; +export const shelleyEraCard: LocatorObject = { locator: '.TransferCards_shelleyEra', method: 'css' }; +export const trezorOption: LocatorObject = { locator: '.fromTrezor_connectTrezor', method: 'css' }; +export const ledgerOption: LocatorObject = { locator: '.fromLedger_connectLedger', method: 'css' }; +export const yoroiPaperButton: LocatorObject = { locator: '.yoroiPaper', method: 'css' }; + +export const icarusTab: LocatorObject = { locator: '.IcarusTab', method: 'css' }; +export const restore15WordWalletIcarus: LocatorObject = { + locator: '.fromIcarusWallet15Word_restoreNormalWallet', + method: 'css', +}; +export const restoreShelley15WordDialog: LocatorObject = { + locator: '.ShelleyOptionDialog_restoreNormalWallet', + method: 'css', +}; +export const restoreShelleyPaperWalletDialog: LocatorObject = { + locator: '.ShelleyOptionDialog_restorePaperWallet', + method: 'css', +}; + +export const keyInput: LocatorObject = { locator: "input[name='key']", method: 'css' }; +export const descryptionPasswordInput: LocatorObject = { + locator: "input[name='decryptionPassword']", + method: 'css', +}; +export const shelleyPrivateKeyInput: LocatorObject = { locator: '.ShelleyOptionDialog_masterKey', method: 'css' }; +export const restoreIcarusPaperWalletOption: LocatorObject = { + locator: '.fromIcarusPaperWallet_restorePaperWallet', + method: 'css', +}; + +export const transferSummaryPage: LocatorObject = { locator: '.TransferSummaryPage_body', method: 'css' }; + +export const transferErrorPageTitle: LocatorObject = { locator: '.ErrorPage_title', method: 'css' }; +export const transferSuccessPageTitle: LocatorObject = { locator: '.SuccessPage_title', method: 'css' }; + +export const transferButton: LocatorObject = { locator: '.transferButton', method: 'css' }; +export const createYoroiWalletButton: LocatorObject = { + locator: '.createYoroiWallet.YoroiTransferStartPage_button', + method: 'css', +}; +export const transferSummaryPageError: LocatorObject = { locator: '.TransferSummaryPage_error', method: 'css' }; +export const keepRegisteredButton: LocatorObject = { + locator: `//button[contains(text(), "Keep registered")]`, + method: 'xpath', +}; +export const transferSummaryRefundText: LocatorObject = { locator: '.TransferSummaryPage_refund', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletDashboardPage.js b/packages/yoroi-extension/features/pages/walletDashboardPage.js index aa61194807..7b620cf796 100644 --- a/packages/yoroi-extension/features/pages/walletDashboardPage.js +++ b/packages/yoroi-extension/features/pages/walletDashboardPage.js @@ -5,5 +5,8 @@ import type { LocatorObject } from '../support/webdriver'; export const withdrawButton: LocatorObject = { locator: '.withdrawButton', method: 'css' }; export const rechartBar: LocatorObject = { locator: '.recharts-bar', method: 'css' }; -export const mangledWarningIcon: LocatorObject = { locator: '.UserSummary_mangledWarningIcon', method: 'css' }; +export const mangledWarningIcon: LocatorObject = { + locator: '.UserSummary_mangledWarningIcon', + method: 'css', +}; export const userSummaryLink: LocatorObject = { locator: '.UserSummary_link', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletDelegationPage.js b/packages/yoroi-extension/features/pages/walletDelegationPage.js new file mode 100644 index 0000000000..8cdb8b5f41 --- /dev/null +++ b/packages/yoroi-extension/features/pages/walletDelegationPage.js @@ -0,0 +1,30 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const delegationTxDialogError: LocatorObject = { + locator: '.DelegationTxDialog_error', + method: 'css', +}; +export const poolIdInput: LocatorObject = { locator: "input[name='poolId']", method: 'css' }; +export const stakePoolTicker: LocatorObject = { locator: '.StakePool_userTitle', method: 'css' }; +export const delegationFormNextButton: LocatorObject = { + locator: '.DelegationSendForm_component .MuiButton-primary', + method: 'css', +}; +export const delegationTxDialog: LocatorObject = { + locator: '.DelegationTxDialog_dialog', + method: 'css', +}; +export const delegationSuccessPage: LocatorObject = { + locator: '.SuccessPage_component', + method: 'css', +}; +export const delegationDashboardPageButton: LocatorObject = { + locator: "//button[contains(text(), 'Dashboard page')]", + method: 'xpath', +}; +export const delegationDashboardPage: LocatorObject = { + locator: '.StakingDashboard_page', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/walletPage.js b/packages/yoroi-extension/features/pages/walletPage.js index 5d74816da8..058d8b140c 100644 --- a/packages/yoroi-extension/features/pages/walletPage.js +++ b/packages/yoroi-extension/features/pages/walletPage.js @@ -1,18 +1,46 @@ // @flow // Revamped wallets list elements -export const summaryTab = { locator: 'summary', method: 'css' }; -export const sendTab = { locator: '.send', method: 'css' }; -export const receiveTab = { locator: '.receive', method: 'css' }; -export const claimTransferTab = { locator: '.claimTransfer', method: 'css' }; +import type { LocatorObject } from '../support/webdriver'; -export const walletNameText = { locator: '.NavPlate_name', method: 'css' }; -export const activeNavTab = { locator: '.WalletNavButton_active', method: 'css' }; -export const dashboardTab = { locator: '.stakeDashboard ', method: 'css' }; -export const transactionsTab = { locator: `//span[contains(text(), "Transactions")]`, method: 'xpath' }; +export const summaryTab: LocatorObject = { locator: 'summary', method: 'css' }; +export const sendTab: LocatorObject = { locator: '.send', method: 'css' }; +export const receiveTab: LocatorObject = { locator: '.receive', method: 'css' }; +export const claimTransferTab: LocatorObject = { locator: '.claimTransfer', method: 'css' }; +export const votingTab: LocatorObject = { locator: '.voting', method: 'css' }; +export const delegationByIdTab: LocatorObject = { locator: '.cardanoStake', method: 'css' }; -export const navDetailsAmount = { locator: '.NavWalletDetails_amount', method: 'css' }; -export const navDetailsHideButton = { locator: '.NavWalletDetails_toggleButton', method: 'css' }; -export const navDetailsWalletDropdown = { locator: '.NavDropdown_toggle', method: 'css' }; -export const navDetailsBuyButton = { locator: '.NavDropdownContent_buyButton', method: 'css' }; -export const buyDialogAddress = { locator: '.BuySellDialog_address', method: 'css' }; \ No newline at end of file +export const walletNameText: LocatorObject = { locator: '.NavPlate_name', method: 'css' }; +export const activeNavTab: LocatorObject = { locator: '.WalletNavButton_active', method: 'css' }; +export const dashboardTab: LocatorObject = { locator: '.stakeDashboard ', method: 'css' }; +export const transactionsTab: LocatorObject = { + locator: `//span[contains(text(), "Transactions")]`, + method: 'xpath', +}; + +export const navDetailsAmount: LocatorObject = { + locator: '.NavWalletDetails_amount', + method: 'css', +}; +export const navDetailsHideButton: LocatorObject = { + locator: '.NavWalletDetails_toggleButton', + method: 'css', +}; +export const navDetailsWalletDropdown: LocatorObject = { + locator: '.NavDropdown_toggle', + method: 'css', +}; +export const navDetailsBuyButton: LocatorObject = { + locator: '.NavDropdownContent_buyButton', + method: 'css', +}; +export const buyDialogAddress: LocatorObject = { locator: '.BuySellDialog_address', method: 'css' }; +export const addAdditionalWalletButton: LocatorObject = { + locator: `.NavBar_navbar .NavBar_content .MuiButton-primary`, + method: 'css', +}; + +export const walletNavBackButton: LocatorObject = { + locator: '.NavBar_navbar .NavBar_title .NavBarBack_backButton', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/walletReceivePage.js b/packages/yoroi-extension/features/pages/walletReceivePage.js index e87c696336..060259f236 100644 --- a/packages/yoroi-extension/features/pages/walletReceivePage.js +++ b/packages/yoroi-extension/features/pages/walletReceivePage.js @@ -16,11 +16,65 @@ export const getAddressLocator = (address: string): LocatorObject => { }; }; -export const addressErrorPhrase = { locator: '.StandardHeader_error', method: 'css' }; -export const generateAddressButton = { locator: '.generateAddressButton', method: 'css' }; -export const addressBookTab = { locator: `.addressBook`, method: 'css' }; -export const rewardAddressTab = { locator: `.reward`, method: 'css' }; -export const yourWalletAddrHeader = { locator: '.StandardHeader_copyableHash', method: 'css' }; -export const verifyAddressButton = { locator: '.WalletReceive_verifyIcon', method: 'css' }; +export const addressErrorPhrase: LocatorObject = { + locator: '.StandardHeader_error', + method: 'css', +}; +export const generateAddressButton: LocatorObject = { + locator: '.generateAddressButton', + method: 'css', +}; +export const addressBookTab: LocatorObject = { locator: `.addressBook`, method: 'css' }; +export const rewardAddressTab: LocatorObject = { locator: `.reward`, method: 'css' }; +export const yourWalletAddrHeader: LocatorObject = { + locator: '.StandardHeader_copyableHash', + method: 'css', +}; +export const verifyAddressButton: LocatorObject = { + locator: '.WalletReceive_verifyIcon', + method: 'css', +}; -export const verifyAddressHWButton = { locator: '.VerifyAddressDialog_component .primary', method: 'css' }; +export const verifyAddressHWButton: LocatorObject = { + locator: '.VerifyAddressDialog_component .primary', + method: 'css', +}; +export const unmangleButton: LocatorObject = { + locator: '.MangledHeader_submitButton ', + method: 'css', +}; + +export const generateUriIcon: LocatorObject = { + locator: '.WalletReceive_generateURIIcon', + method: 'css', +}; +export const uriGenerateDialog: LocatorObject = { locator: '.URIGenerateDialog', method: 'css' }; +export const generateUriButton: LocatorObject = { + locator: '.URIGenerateDialog_component .MuiButton-primary', + method: 'css', +}; +export const uriDisplayDialog: LocatorObject = { locator: '.URIDisplayDialog', method: 'css' }; +export const copyToClipboardIcon: LocatorObject = { + locator: '.URIDisplayDialog_uriDisplay .CopyableAddress_copyIconBig', + method: 'css', +}; +export const uriLandingDialog: LocatorObject = { locator: '.URILandingDialog', method: 'css' }; +export const uriLandingDialogAcceptButton: LocatorObject = { + locator: '.URILandingDialog .MuiButton-primary', + method: 'css', +}; + +export const uriVerifyDialog: LocatorObject = { locator: '.URIVerifyDialog', method: 'css' }; +export const uriVerifyDialogAddress: LocatorObject = { + locator: '.URIVerifyDialog_address', + method: 'css', +}; +export const uriVerifyDialogAmount: LocatorObject = { + locator: '.URIVerifyDialog_amount', + method: 'css', +}; +export const uriDetailsConfirmButton: LocatorObject = { + locator: '.URIVerifyDialog .primary', + method: 'css', +}; +export const invalidUriDialog: LocatorObject = { locator: '.URIInvalidDialog', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js index 8cbbeb2f7a..f2e601ffe6 100644 --- a/packages/yoroi-extension/features/pages/walletSendPage.js +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -1,6 +1,75 @@ // @flow -export const receiverInput = { locator: "input[name='receiver']", method: 'css' }; -export const amountInput = { locator: "input[name='amount']", method: 'css' }; -export const addMemoButton = { locator: '.addMemoButton', method: 'css' }; -export const memoContentInput = { locator: "input[name='memoContent']", method: 'css' }; +import type { LocatorObject } from '../support/webdriver'; + +export const assetSelector: LocatorObject = { + locator: '.WalletSendForm_component .SimpleInput_input', + method: 'css', +}; +export const assetListElement: LocatorObject = { + locator: '.TokenOptionRow_item_name', + method: 'css', +}; +export const receiverInput: LocatorObject = { locator: "input[name='receiver']", method: 'css' }; +export const amountInput: LocatorObject = { locator: "input[name='amount']", method: 'css' }; +export const addMemoButton: LocatorObject = { locator: '.addMemoButton', method: 'css' }; +export const memoContentInput: LocatorObject = { + locator: "input[name='memoContent']", + method: 'css', +}; +export const nextButton: LocatorObject = { + locator: '.WalletSendForm_component .MuiButton-primary', + method: 'css', +}; +export const invalidAddressError: LocatorObject = { + locator: '.receiver .SimpleInput_errored', + method: 'css', +}; +export const notEnoughAdaError: LocatorObject = { + locator: '.FormFieldOverridesClassic_error', + method: 'css', +}; + +export const sendAllCheckbox: LocatorObject = { + locator: '.WalletSendForm_checkbox', + method: 'css', +}; +export const sendMoneyConfirmationDialog: LocatorObject = { + locator: '.WalletSendConfirmationDialog_dialog', + method: 'css', +}; +export const submitButton: LocatorObject = { locator: '.confirmButton', method: 'css' }; +export const disabledSubmitButton: LocatorObject = { + locator: '.primary.SimpleButton_disabled', + method: 'css', +}; + +export const successPageTitle: LocatorObject = { locator: '.SuccessPage_title', method: 'css' }; +export const transactionPageButton: LocatorObject = { + locator: "//button[contains(text(), 'Transaction page')]", + method: 'xpath', +}; + +// Send confirmation Dialog + +export const sendConfirmationDialogAddressToText: LocatorObject = { + locator: '.WalletSendConfirmationDialog_addressTo', + method: 'css', +}; +export const sendConfirmationDialogFeesText: LocatorObject = { + locator: '.WalletSendConfirmationDialog_fees', + method: 'css', +}; +export const sendConfirmationDialogAmountText: LocatorObject = { + locator: '.WalletSendConfirmationDialog_amount', + method: 'css', +}; +export const sendConfirmationDialogTotalAmountText: LocatorObject = { + locator: '.WalletSendConfirmationDialog_totalAmount', + method: 'css', +}; +export const sendConfirmationDialogError: LocatorObject = { + locator: '.WalletSendConfirmationDialog_error', + method: 'css', +}; +export const warningBox: LocatorObject = { locator: '.WarningBox_warning', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletTransactionsPage.js b/packages/yoroi-extension/features/pages/walletTransactionsPage.js index 73041831de..d422c0bfb5 100644 --- a/packages/yoroi-extension/features/pages/walletTransactionsPage.js +++ b/packages/yoroi-extension/features/pages/walletTransactionsPage.js @@ -1,5 +1,37 @@ // @flow -export const walletSummaryBox = { locator: 'walletSummary_box', method: 'id' }; -export const walletSummaryComponent = { locator: "//div[@class='WalletSummary_component']", method: 'xpath' }; -export const copyToClipboardButton = { locator: '.CopyableAddress_copyIconBig', method: 'css' }; +import type { LocatorObject } from '../support/webdriver'; + +export const walletSummaryBox: LocatorObject = { locator: 'walletSummary_box', method: 'id' }; +export const walletSummaryComponent: LocatorObject = { + locator: '.WalletSummary_component', + method: 'css', +}; +export const copyToClipboardButton: LocatorObject = { + locator: '.CopyableAddress_copyIconBig', + method: 'css', +}; +export const numberOfTransactions: LocatorObject = { + locator: '.WalletSummary_numberOfTransactions', + method: 'css', +}; +export const noTransactionsComponent: LocatorObject = { + locator: '.WalletNoTransactions_component', + method: 'css', +}; +export const showMoreButton: LocatorObject = { + locator: '.WalletTransactionsList_component .MuiButton-primary', + method: 'css', +}; +export const transactionListElement: LocatorObject = { + locator: '.Transaction_component', + method: 'css', +}; +export const pendingTransactionElement: LocatorObject = { + locator: '.Transaction_pendingLabel', + method: 'css', +}; +export const failedTransactionElement: LocatorObject = { + locator: '.Transaction_failedLabel', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/walletVotingPage.js b/packages/yoroi-extension/features/pages/walletVotingPage.js new file mode 100644 index 0000000000..8398f80f8b --- /dev/null +++ b/packages/yoroi-extension/features/pages/walletVotingPage.js @@ -0,0 +1,48 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const registerButton: LocatorObject = { + locator: '.Voting_registerButton > .primary', + method: 'css', +}; +export const generatePinDialog: LocatorObject = { + locator: '.GeneratePinDialog_dialog', + method: 'css', +}; +export const generatedPinButton: LocatorObject = { + locator: '.GeneratePinDialog_dialog > .Dialog_actions > .primary', + method: 'css', +}; +export const confirmPinDialog: LocatorObject = { + locator: '.ConfirmPinDialog_dialog', + method: 'css', +}; + +export const pinInput: LocatorObject = { locator: "input[name='pin']", method: 'css' }; +export const confirmPinButton: LocatorObject = { + locator: '.ConfirmPinDialog_dialog > .Dialog_actions > .primary', + method: 'css', +}; +export const confirmPinDialogError: LocatorObject = { + locator: + '.ConfirmPinDialog_dialog .ConfirmPinDialog_pinInputContainer .FormFieldOverridesClassic_error', + method: 'css', +}; + +export const registerDialog: LocatorObject = { locator: '.RegisterDialog_dialog', method: 'css' }; +export const registerDialogNextButton: LocatorObject = { + locator: '.RegisterDialog_dialog > .Dialog_actions > .primary', + method: 'css', +}; + +export const votingRegTxDialog: LocatorObject = { + locator: '.VotingRegTxDialog_dialog', + method: 'css', +}; +export const votingRegTxDialogError: LocatorObject = { + locator: '.VotingRegTxDialog_error', + method: 'css', +}; +export const qrCodeDialog: LocatorObject = { locator: '.QrCodeDialog_dialog', method: 'css' }; +export const errorBlock: LocatorObject = { locator: '.ErrorBlock_component > span', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletsListPage.js b/packages/yoroi-extension/features/pages/walletsListPage.js index bc282a8838..e0717ce66e 100644 --- a/packages/yoroi-extension/features/pages/walletsListPage.js +++ b/packages/yoroi-extension/features/pages/walletsListPage.js @@ -1,16 +1,18 @@ // @flow +import type { LocatorObject } from '../support/webdriver'; + import { WebElement } from 'selenium-webdriver'; import { getMethod } from '../support/helpers/helpers'; import { NoSuchElementError } from 'selenium-webdriver/lib/error'; -const walletRow = { locator: '.WalletRow_content', method: 'css' }; -const walletPlateNumber = { +const walletRow: LocatorObject = { locator: '.WalletRow_content', method: 'css' }; +const walletPlateNumber: LocatorObject = { locator: '.WalletRow_nameSection .NavPlate_wrapper .NavPlate_content .NavPlate_head .NavPlate_plate', method: 'css', }; -const walletButton = { +const walletButton: LocatorObject = { locator: '//button[@class="WalletRow_nameSection" and @type="button"]', method: 'xpath', }; diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 62fe626015..aa7b7a1b0e 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -36,7 +36,6 @@ import { walletButton } from '../pages/sidebarPage'; import { getWalletButtonByPlate } from '../pages/walletsListPage'; import { connectHwButton, - restoreWalletButton, getCurrencyButton, pickUpCurrencyDialog, hwOptionsDialog, @@ -54,12 +53,14 @@ import { walletRestoreDialog, pickUpCurrencyDialogCardano, byronEraButton, + walletAddRestoreWalletButton, } from '../pages/newWalletPages'; import { allowPubKeysAndSwitchToYoroi, switchToTrezorAndAllow } from './trezor-steps'; import * as helpers from '../support/helpers/helpers'; import { confirmButton, repeatPasswordInput, + restoreWalletButton, walletPasswordInput, } from '../pages/restoreWalletPage'; import { walletNameText } from '../pages/walletPage'; @@ -304,7 +305,7 @@ Given(/^There is an Ergo wallet stored named ([^"]*)$/, async function (walletNa const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); - await this.click(restoreWalletButton); + await this.click(walletAddRestoreWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogErgo); @@ -322,7 +323,7 @@ Given(/^There is a Shelley wallet stored named ([^"]*)$/, async function (wallet const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); - await this.click(restoreWalletButton); + await this.click(walletAddRestoreWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); @@ -341,7 +342,7 @@ Given(/^There is a Byron wallet stored named ([^"]*)$/, async function (walletNa const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); - await this.click(restoreWalletButton); + await this.click(walletAddRestoreWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(getCurrencyButton('cardano')); @@ -359,7 +360,7 @@ Given(/^I have completed the basic setup$/, async function () { this.webDriverLogger.info(`Step: I have completed the basic setup`); // language select page await this.waitForElement(languageSelectionForm); - await this.click(); + await this.click(continueButton); // ToS page await this.waitForElement(termsOfUseComponent); const tosClassElement = await this.driver.findElement(By.css('.TermsOfUseForm_component')); diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index 3f828c1036..ef24b462b8 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -11,8 +11,32 @@ import { networks, defaultAssets, } from '../../app/api/ada/lib/storage/database/prepackaged/networks'; -import { walletSummaryBox } from '../pages/walletTransactionsPage'; -import { amountInput, receiverInput } from '../pages/walletSendPage'; +import { walletSummaryBox, walletSummaryComponent } from '../pages/walletTransactionsPage'; +import { + amountInput, + assetListElement, + assetSelector, + disabledSubmitButton, + invalidAddressError, + nextButton, + notEnoughAdaError, + receiverInput, + sendAllCheckbox, + sendConfirmationDialogAddressToText, + sendConfirmationDialogAmountText, + sendConfirmationDialogError, + sendConfirmationDialogFeesText, + sendConfirmationDialogTotalAmountText, + sendMoneyConfirmationDialog, + submitButton, + successPageTitle, + transactionPageButton, + warningBox, +} from '../pages/walletSendPage'; +import { sendTab } from '../pages/walletPage'; +import { walletPasswordInput } from '../pages/restoreWalletPage'; +import { delegationTxDialogError } from '../pages/walletDelegationPage'; +import { unmangleButton } from '../pages/walletReceivePage'; Given(/^I have a wallet with funds$/, async function () { const amountWithCurrency = await this.driver.findElements( @@ -25,7 +49,7 @@ Given(/^I have a wallet with funds$/, async function () { }); When(/^I go to the send transaction screen$/, async function () { - await this.click({ locator: '.send', method: 'css' }); + await this.click(sendTab); }); When(/^I fill the form:$/, async function (table) { @@ -47,18 +71,15 @@ When(/^I see CONFIRM TRANSACTION Pop up:$/, async function (table) { const fields = table.hashes()[0]; const total = parseFloat(fields.amount) + parseFloat(fields.fee); - await this.waitUntilText( - { locator: '.WalletSendConfirmationDialog_addressTo', method: 'css' }, - truncateAddress(fields.address) - ); - await this.waitUntilContainsText({ locator: '.WalletSendConfirmationDialog_fees', method: 'css' }, fields.fee); - await this.waitUntilContainsText({ locator: '.WalletSendConfirmationDialog_amount', method: 'css' }, fields.amount); + await this.waitUntilText(sendConfirmationDialogAddressToText, truncateAddress(fields.address)); + await this.waitUntilContainsText(sendConfirmationDialogFeesText, fields.fee); + await this.waitUntilContainsText(sendConfirmationDialogAmountText, fields.amount); const network = networks.CardanoMainnet; const assetInfo = defaultAssets.filter(asset => asset.NetworkId === network.NetworkId)[0]; const decimalPlaces = assetInfo.Metadata.numberOfDecimals; await this.waitUntilContainsText( - { locator: '.WalletSendConfirmationDialog_totalAmount', method: 'css' }, + sendConfirmationDialogTotalAmountText, total.toFixed(decimalPlaces) ); }); @@ -68,7 +89,7 @@ When(/^I clear the receiver$/, async function () { }); When(/^I clear the wallet password ([^"]*)$/, async function (password) { - await this.clearInputUpdatingForm({ locator: "input[name='walletPassword']", method: 'css' }, password.length); + await this.clearInputUpdatingForm(walletPasswordInput, password.length); }); When(/^I fill the receiver as "([^"]*)"$/, async function (receiver) { @@ -82,14 +103,13 @@ When(/^The transaction fees are "([^"]*)"$/, async function (fee) { .findElement(By.xpath('//p')); const messageText = await messageElement.getText(); return messageText === `+ ${fee} of fees`; - }) + }); expect(result).to.be.true; }); When(/^I click on the next button in the wallet send form$/, async function () { - const button = '.WalletSendForm_component .MuiButton-primary'; - await this.waitForElement({ locator: button, method: 'css' }); - await this.click({ locator: button, method: 'css' }); + await this.waitForElement(nextButton); + await this.click(nextButton); /** * Sometimes out tests fail because clicking this button isn't triggering a dialog * However it works flawlessly both locally and on localci @@ -101,7 +121,7 @@ When(/^I click on the next button in the wallet send form$/, async function () { */ await this.driver.sleep(500); try { - await this.click({ locator: button, method: 'css' }); + await this.click(nextButton); } catch (e) { // if the first click succeeded, the second will throw an exception // saying that the button can't be clicked because a dialog is in the way @@ -109,72 +129,72 @@ When(/^I click on the next button in the wallet send form$/, async function () { }); When(/^I click on "Send all" checkbox$/, async function () { - await this.click({ locator: '.WalletSendForm_checkbox', method: 'css' }); + await this.click(sendAllCheckbox); }); When(/^I see send money confirmation dialog$/, async function () { - await this.waitForElement({ locator: '.WalletSendConfirmationDialog_dialog', method: 'css' }); + await this.waitForElement(sendMoneyConfirmationDialog); }); When(/^I enter the wallet password:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: "input[name='walletPassword']", method: 'css' }, fields.password); + await this.input(walletPasswordInput, fields.password); }); When(/^I submit the wallet send form$/, async function () { - await this.click({ locator: '.confirmButton', method: 'css' }); + await this.click(submitButton); }); Then(/^I should see the successfully sent page$/, async function () { - await this.waitForElement({ locator: '.SuccessPage_title', method: 'css' }); + await this.waitForElement(successPageTitle); }); Then(/^I click the transaction page button$/, async function () { - await this.click({ locator: "//button[contains(text(), 'Transaction page')]", method: 'xpath' }); + await this.click(transactionPageButton); }); Then(/^I should see the summary screen$/, async function () { - await this.waitForElement({ locator: '.WalletSummary_component', method: 'css' }); + await this.waitForElement(walletSummaryComponent); }); Then(/^Revamp. I should see the summary screen$/, async function () { await this.waitForElement(walletSummaryBox); -}) +}); Then(/^I should see an invalid address error$/, async function () { - await this.waitForElement({ locator: '.receiver .SimpleInput_errored', method: 'css' }); + await this.waitForElement(invalidAddressError); }); Then(/^I should see a not enough ada error$/, async function () { const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.NotEnoughMoneyToSendError', }); - await this.waitUntilText({ locator: '.FormFieldOverridesClassic_error', method: 'css' }, errorMessage); + await this.waitUntilText(notEnoughAdaError, errorMessage); }); Then(/^I should not be able to submit$/, async function () { - await this.waitForElement({ locator: '.primary.SimpleButton_disabled', method: 'css' }); + await this.waitForElement(disabledSubmitButton); }); Then(/^I should see an invalid signature error message$/, async function () { const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.invalidWitnessError', }); - await this.waitUntilText({ locator: '.WalletSendConfirmationDialog_error', method: 'css' }, errorMessage); + await this.waitUntilText(sendConfirmationDialogError, errorMessage); }); Then(/^I should see an incorrect wallet password error message$/, async function () { const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.IncorrectPasswordError', }); - await this.waitUntilText({ locator: '.WalletSendConfirmationDialog_error', method: 'css' }, errorMessage); + await this.waitUntilText(sendConfirmationDialogError, errorMessage); }); Then(/^I should see an delegation incorrect wallet password error message$/, async function () { const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.IncorrectPasswordError', }); - await this.waitUntilText({ locator: '.DelegationTxDialog_error', method: 'css' }, errorMessage); + await this.waitUntilText(delegationTxDialogError, errorMessage); }); Then(/^A successful tx gets sent from my wallet from another client$/, () => { @@ -188,23 +208,23 @@ Then(/^A pending tx gets sent from my wallet from another client$/, () => { }); Then(/^I should see a warning block$/, async function () { - await this.waitForElement({ locator: '.WarningBox_warning', method: 'css' }); + await this.waitForElement(warningBox); }); Then(/^I should see no warning block$/, async function () { - await this.waitForElementNotPresent({ locator: '.WarningBox_warning', method: 'css' }); + await this.waitForElementNotPresent(warningBox); }); When(/^I click on the unmangle button$/, async function () { - await this.click({ locator: '.MangledHeader_submitButton ', method: 'css' }); + await this.click(unmangleButton); }); When(/^I open the token selection dropdown$/, async function () { - await this.click({ locator: '.WalletSendForm_component .SimpleInput_input', method: 'css' }); + await this.click(assetSelector); }); When(/^I select token "([^"]*)"$/, async function (tokenName) { - const tokenRows = await this.getElementsBy({ locator: '.TokenOptionRow_item_name', method: 'css' }); + const tokenRows = await this.getElementsBy(assetListElement); for (const row of tokenRows) { const name = await row.getText(); if (name === tokenName) { diff --git a/packages/yoroi-extension/features/step_definitions/trezor-steps.js b/packages/yoroi-extension/features/step_definitions/trezor-steps.js index 82d32d4b35..7353bae775 100644 --- a/packages/yoroi-extension/features/step_definitions/trezor-steps.js +++ b/packages/yoroi-extension/features/step_definitions/trezor-steps.js @@ -3,9 +3,9 @@ import { Then } from 'cucumber'; import { extensionTabName, trezorConnectTabName } from '../support/windowManager'; import { - confirmUsingTrezorButton, - dontAskAgainCheckbox, - exportTrezorButton, + confirmUsingTrezorButton, + dontAskAgainCheckbox, + exportTrezorButton, } from '../pages/trezorConnectPage'; import { TrezorEmulatorController } from '../support/trezorEmulatorController'; import { expect } from 'chai'; @@ -13,78 +13,78 @@ import { verifyButton } from '../pages/verifyAddressPage'; import { errorBlockComponent } from '../pages/commonDialogPage'; export async function switchToTrezorAndAllow(customWorld: any) { - // wait for a new tab - await customWorld.windowManager.findNewWindowAndSwitchTo(trezorConnectTabName); - // tick the checkbox on the Trezor page and press Allow button - await customWorld.driver.sleep(1000); - await customWorld.waitForElement(dontAskAgainCheckbox); - await customWorld.click(dontAskAgainCheckbox); - await customWorld.waitForElement(confirmUsingTrezorButton); - await customWorld.click(confirmUsingTrezorButton); + // wait for a new tab + await customWorld.windowManager.findNewWindowAndSwitchTo(trezorConnectTabName); + // tick the checkbox on the Trezor page and press Allow button + await customWorld.driver.sleep(1000); + await customWorld.waitForElement(dontAskAgainCheckbox); + await customWorld.click(dontAskAgainCheckbox); + await customWorld.waitForElement(confirmUsingTrezorButton); + await customWorld.click(confirmUsingTrezorButton); } export async function switchToTrezorAndExport(customWorld: any) { - // wait for a new tab - await customWorld.windowManager.findNewWindowAndSwitchTo(trezorConnectTabName); - // tick the checkbox on the Trezor page and press Allow button - await customWorld.driver.sleep(1000); - await customWorld.waitForElement(confirmUsingTrezorButton); - await customWorld.click(confirmUsingTrezorButton); + // wait for a new tab + await customWorld.windowManager.findNewWindowAndSwitchTo(trezorConnectTabName); + // tick the checkbox on the Trezor page and press Allow button + await customWorld.driver.sleep(1000); + await customWorld.waitForElement(confirmUsingTrezorButton); + await customWorld.click(confirmUsingTrezorButton); } export async function allowPubKeysAndSwitchToYoroi(customWorld: any) { - // press the Export button - await customWorld.click(exportTrezorButton); - // wait for closing the new tab - await customWorld.windowManager.waitForClosingAndSwitchTo(trezorConnectTabName, extensionTabName); + // press the Export button + await customWorld.click(exportTrezorButton); + // wait for closing the new tab + await customWorld.windowManager.waitForClosingAndSwitchTo(trezorConnectTabName, extensionTabName); } Then(/^I switch to Trezor-connect screen and allow using$/, async function () { - await switchToTrezorAndAllow(this); + await switchToTrezorAndAllow(this); }); Then(/^I press Yes on the Trezor emulator$/, async function () { - for (let i = 1; i < 4; i++) { - const pressYesResponse = await this.trezorController.emulatorPressYes(); - expect(pressYesResponse.success, `${i} emulator-press-yes request is failed`).to.be.true; - } - await this.windowManager.waitForClosingAndSwitchTo(trezorConnectTabName, extensionTabName); + for (let i = 1; i < 4; i++) { + const pressYesResponse = await this.trezorController.emulatorPressYes(); + expect(pressYesResponse.success, `${i} emulator-press-yes request is failed`).to.be.true; + } + await this.windowManager.waitForClosingAndSwitchTo(trezorConnectTabName, extensionTabName); }); Then(/^I connect to trezor controller$/, async function () { - this.trezorController = new TrezorEmulatorController(this.trezorEmuLogger); - await this.trezorController.connect(); - const result = await this.trezorController.getLastEvent(); - expect(result.type).to.be.equal('client', 'Something is wrong with connection'); + this.trezorController = new TrezorEmulatorController(this.trezorEmuLogger); + await this.trezorController.connect(); + const result = await this.trezorController.getLastEvent(); + expect(result.type).to.be.equal('client', 'Something is wrong with connection'); }); Then(/^I start trezor emulator environment$/, async function () { - const pingResponse = await this.trezorController.ping(); - expect(pingResponse.success, 'Ping request is failed').to.be.true; + const pingResponse = await this.trezorController.ping(); + expect(pingResponse.success, 'Ping request is failed').to.be.true; - const bridgeStartResponse = await this.trezorController.bridgeStart(); - expect(bridgeStartResponse.success, 'bridge-start request is failed').to.be.true; + const bridgeStartResponse = await this.trezorController.bridgeStart(); + expect(bridgeStartResponse.success, 'bridge-start request is failed').to.be.true; - const emulatorStartResponse = await this.trezorController.emulatorStart(); - expect(emulatorStartResponse.success, 'emulator-start request is failed').to.be.true; + const emulatorStartResponse = await this.trezorController.emulatorStart(); + expect(emulatorStartResponse.success, 'emulator-start request is failed').to.be.true; - const emulatorWipeResponse = await this.trezorController.emulatorWipe(); - expect(emulatorWipeResponse.success, 'emulator-wipe request is failed').to.be.true; + const emulatorWipeResponse = await this.trezorController.emulatorWipe(); + expect(emulatorWipeResponse.success, 'emulator-wipe request is failed').to.be.true; - const emulatorSetupResponse = await this.trezorController.emulatorSetup( - 'lyrics tray aunt muffin brisk ensure wedding cereal capital path replace weasel' - ); - expect(emulatorSetupResponse.success, 'emulator-setup request is failed').to.be.true; + const emulatorSetupResponse = await this.trezorController.emulatorSetup( + 'lyrics tray aunt muffin brisk ensure wedding cereal capital path replace weasel' + ); + expect(emulatorSetupResponse.success, 'emulator-setup request is failed').to.be.true; }); Then(/^I verify the address on the trezor emulator$/, async function () { - await this.click(verifyButton); - await switchToTrezorAndExport(this); - for (let i = 1; i < 4; i++) { - const pressYesResponse = await this.trezorController.emulatorPressYes(); - expect(pressYesResponse.success, `${i} emulator-press-yes request is failed`).to.be.true; - } - await this.windowManager.waitForClosingAndSwitchTo(trezorConnectTabName, extensionTabName); - // we should have this disable while the action is processing, but we don't show a spinner on this - await this.waitForElementNotPresent(errorBlockComponent); -}); \ No newline at end of file + await this.click(verifyButton); + await switchToTrezorAndExport(this); + for (let i = 1; i < 4; i++) { + const pressYesResponse = await this.trezorController.emulatorPressYes(); + expect(pressYesResponse.success, `${i} emulator-press-yes request is failed`).to.be.true; + } + await this.windowManager.waitForClosingAndSwitchTo(trezorConnectTabName, extensionTabName); + // we should have this disable while the action is processing, but we don't show a spinner on this + await this.waitForElementNotPresent(errorBlockComponent); +}); diff --git a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js index 02ea1952b2..a9d2e0b907 100644 --- a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js +++ b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js @@ -5,10 +5,20 @@ import { By } from 'selenium-webdriver'; import chai from 'chai'; import moment from 'moment'; import i18n from '../support/helpers/i18n-helpers'; +import { failedTransactionElement, noTransactionsComponent, numberOfTransactions, pendingTransactionElement, showMoreButton, transactionListElement } from '../pages/walletTransactionsPage'; +import { summaryTab } from '../pages/walletPage'; function verifyAllTxsFields( - txType, txAmount, txTime, txStatus, txFee, txFromList, txToList, - txId, expectedTx, txConfirmations + txType, + txAmount, + txTime, + txStatus, + txFee, + txFromList, + txToList, + txId, + expectedTx, + txConfirmations ) { chai.expect(txType).to.equal(expectedTx.txType); chai.expect(txAmount.split(' ')[0]).to.equal(expectedTx.txAmount); @@ -41,71 +51,60 @@ When(/^I see the transactions summary$/, async function () { // sometimes this UI twitches on load when it starts fetching data from the server // sleep to avoid the twitch breaking the test await this.driver.sleep(500); - await this.waitForElement({ locator: '.WalletSummary_numberOfTransactions', method: 'css' }); + await this.waitForElement(numberOfTransactions); }); Then( /^I should see that the number of transactions is ([^"]*)$/, async function (expectedTxsNumber) { - const txsNumberMessage = await i18n.formatMessage(this.driver, - { id: 'wallet.summary.page.transactionsLabel' }); - await this.waitUntilText( - { locator: '.WalletSummary_numberOfTransactions', method: 'css' }, - txsNumberMessage + ': ' + expectedTxsNumber - ); + const txsNumberMessage = await i18n.formatMessage(this.driver, { + id: 'wallet.summary.page.transactionsLabel', + }); + await this.waitUntilText(numberOfTransactions, txsNumberMessage + ': ' + expectedTxsNumber); } ); - Then(/^I should see no transactions$/, async function () { - await this.waitForElement({ locator: '.WalletNoTransactions_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); + await this.waitForElement(noTransactionsComponent); + const actualTxsList = await this.getElementsBy(transactionListElement); chai.expect(actualTxsList.length).to.equal(0); }); -Then( - /^I should see ([^"]*) ([^"]*) transactions$/, - async function (txsNumber, txExpectedStatus) { - const txsAmount = parseInt(txsNumber, 10); - const showMoreLocator = '.WalletTransactionsList_component .MuiButton-primary'; +Then(/^I should see ([^"]*) ([^"]*) transactions$/, async function (txsNumber, txExpectedStatus) { + const txsAmount = parseInt(txsNumber, 10); - await this.driver.sleep(500); - // press the show more transaction button until all transactions are visible - for (let i = 1; i < txsAmount; i++) { - const buttonShowMoreExists = await this.checkIfExists({ locator: showMoreLocator, method: 'css' }); - if (!buttonShowMoreExists) { - break; - } - await this.click({ locator: showMoreLocator, method: 'css' }); - await this.driver.sleep(500); + await this.driver.sleep(500); + // press the show more transaction button until all transactions are visible + for (let i = 1; i < txsAmount; i++) { + const buttonShowMoreExists = await this.checkIfExists(showMoreButton); + if (!buttonShowMoreExists) { + break; } + await this.click(showMoreButton); + await this.driver.sleep(500); + } - const allTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); - const pendingTxsList = await this.getElementsBy({ locator: '.Transaction_pendingLabel', method: 'css' }); - const failedTxsList = await this.getElementsBy({ locator: '.Transaction_failedLabel', method: 'css' }); - if (txExpectedStatus === 'pending') { - chai.expect(pendingTxsList.length).to.equal(txsAmount); - return; - } - if (txExpectedStatus === 'failed') { - chai.expect(failedTxsList.length).to.equal(txsAmount); - return; - } - chai.expect(allTxsList.length - pendingTxsList.length - failedTxsList.length) - .to.equal(txsAmount); + const allTxsList = await this.getElementsBy(transactionListElement); + const pendingTxsList = await this.getElementsBy(pendingTransactionElement); + const failedTxsList = await this.getElementsBy(failedTransactionElement); + if (txExpectedStatus === 'pending') { + chai.expect(pendingTxsList.length).to.equal(txsAmount); + return; } -); + if (txExpectedStatus === 'failed') { + chai.expect(failedTxsList.length).to.equal(txsAmount); + return; + } + chai.expect(allTxsList.length - pendingTxsList.length - failedTxsList.length).to.equal(txsAmount); +}); -When( - /^I expand the top transaction$/, - async function () { - await this.waitForElement({ locator: '.Transaction_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); - const topTx = actualTxsList[0]; +When(/^I expand the top transaction$/, async function () { + await this.waitForElement(transactionListElement); + const actualTxsList = await this.getElementsBy(transactionListElement); + const topTx = actualTxsList[0]; - await topTx.click(); - } -); + await topTx.click(); +}); async function parseTxInfo(addressList) { const addressInfoRow = await addressList.findElements(By.css('.Transaction_addressItem')); @@ -120,65 +119,69 @@ async function parseTxInfo(addressList) { return result; } -Then( - /^I verify top transaction content ([^"]*)$/, - async function (walletName) { - await this.waitForElement({ locator: '.Transaction_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); - const topTx = actualTxsList[0]; - - let status = 'successful'; - { - const pending = await topTx.findElements(By.css('.Transaction_pendingLabel')); - const failed = await topTx.findElements(By.css('.Transaction_failedLabel')); - if (pending.length > 0) { - status = 'pending'; - } else if (failed.length > 0) { - status = 'failed'; - } +Then(/^I verify top transaction content ([^"]*)$/, async function (walletName) { + await this.waitForElement(transactionListElement); + const actualTxsList = await this.getElementsBy(transactionListElement); + const topTx = actualTxsList[0]; + + let status = 'successful'; + { + const pending = await topTx.findElements(By.css('.Transaction_pendingLabel')); + const failed = await topTx.findElements(By.css('.Transaction_failedLabel')); + if (pending.length > 0) { + status = 'pending'; + } else if (failed.length > 0) { + status = 'failed'; } + } - await topTx.click(); + await topTx.click(); - const txList = await topTx.findElements(By.css('.Transaction_addressList')); - const fromTxInfo = await parseTxInfo(txList[0]); - const toTxInfo = await parseTxInfo(txList[1]); + const txList = await topTx.findElements(By.css('.Transaction_addressList')); + const fromTxInfo = await parseTxInfo(txList[0]); + const toTxInfo = await parseTxInfo(txList[1]); - const txData = await topTx.getText(); - const txDataFields = txData.split('\n'); - const [txTime, txType, txStatus, txFee, txAmount] = txDataFields; + const txData = await topTx.getText(); + const txDataFields = txData.split('\n'); + const [txTime, txType, txStatus, txFee, txAmount] = txDataFields; - const expectedTx = displayInfo[walletName]; + const expectedTx = displayInfo[walletName]; - const txId = await (async () => { - const elem = await topTx.findElement(By.css('.txid')); - return await elem.getText(); - })(); - const txConfirmation = - status === 'successful' - ? await (async () => { + const txId = await (async () => { + const elem = await topTx.findElement(By.css('.txid')); + return await elem.getText(); + })(); + const txConfirmation = + status === 'successful' + ? await (async () => { const txConfirmationsCount = await topTx.findElement(By.css('.confirmationCount')); const txConfirmationParentElem = await txConfirmationsCount.findElement(By.xpath('./..')); return await txConfirmationParentElem.getText(); })() - : undefined; - - verifyAllTxsFields(txType, txAmount, txTime, txStatus, txFee, fromTxInfo, - toTxInfo, txId, expectedTx, txConfirmation); - } -); + : undefined; + + verifyAllTxsFields( + txType, + txAmount, + txTime, + txStatus, + txFee, + fromTxInfo, + toTxInfo, + txId, + expectedTx, + txConfirmation + ); +}); -Then( - /^The number of confirmations of the top tx is ([^"]*)$/, - async function (count) { - await this.waitForElement({ locator: '.Transaction_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); - const topTx = actualTxsList[0]; - const assuranceElem = await topTx.findElements(By.css('.confirmationCount')); - const confirmationCount = await assuranceElem[0].getText(); - chai.expect(confirmationCount).to.equal(count); - } -); +Then(/^The number of confirmations of the top tx is ([^"]*)$/, async function (count) { + await this.waitForElement(transactionListElement); + const actualTxsList = await this.getElementsBy(transactionListElement); + const topTx = actualTxsList[0]; + const assuranceElem = await topTx.findElements(By.css('.confirmationCount')); + const confirmationCount = await assuranceElem[0].getText(); + chai.expect(confirmationCount).to.equal(count); +}); const displayInfo = { 'many-tx-wallet': { @@ -186,9 +189,7 @@ const displayInfo = { txAmount: '-0.169999', txTime: '2019-04-21T15:13:33.000Z', txStatus: 'HIGH', - txFrom: [ - ['Ae2tdPwUPE...VWfitHfUM9', 'BYRON - INTERNAL', '-0.82 ADA'], - ], + txFrom: [['Ae2tdPwUPE...VWfitHfUM9', 'BYRON - INTERNAL', '-0.82 ADA']], txTo: [ ['Ae2tdPwUPE...iLjTnt34Aj', 'BYRON - EXTERNAL', '+0.000001 ADA'], ['Ae2tdPwUPE...BA7XbSMhKd', 'BYRON - INTERNAL', '+0.65 ADA'], @@ -202,12 +203,8 @@ const displayInfo = { txAmount: '-0.999999', txTime: '2019-04-20T23:14:52.000Z', txStatus: 'PENDING', - txFrom: [ - ['Ae2tdPwUPE...e1cT2aGdSJ', 'BYRON - EXTERNAL', '-1 ADA'], - ], - txTo: [ - ['Ae2tdPwUPE...sTrQfTxPVX', 'PROCESSING...', '+0.000001 ADA'] - ], + txFrom: [['Ae2tdPwUPE...e1cT2aGdSJ', 'BYRON - EXTERNAL', '-1 ADA']], + txTo: [['Ae2tdPwUPE...sTrQfTxPVX', 'PROCESSING...', '+0.000001 ADA']], txId: 'fa6f2c82fb511d0cc9c12a540b5fac6e5a9b0f288f2d140f909f981279e16fbe', txFee: '0.999999', }, @@ -216,9 +213,7 @@ const displayInfo = { txAmount: '-0.18', txTime: '2019-04-20T23:14:51.000Z', txStatus: 'FAILED', - txFrom: [ - ['Ae2tdPwUPE...gBfkkDNBNv', 'BYRON - EXTERNAL', '-1 ADA'], - ], + txFrom: [['Ae2tdPwUPE...gBfkkDNBNv', 'BYRON - EXTERNAL', '-1 ADA']], txTo: [ ['Ae2tdPwUPE...xJPmFzi6G2', 'ADDRESS BOOK', '+0.000001 ADA'], ['Ae2tdPwUPE...bL4UYPN3eU', 'BYRON - INTERNAL', '+0.82 ADA'], @@ -229,5 +224,5 @@ const displayInfo = { }; When(/^I go to the tx history screen$/, async function () { - await this.click({ locator: '.summary ', method: 'css' }); + await this.click(summaryTab); }); diff --git a/packages/yoroi-extension/features/step_definitions/uri-steps.js b/packages/yoroi-extension/features/step_definitions/uri-steps.js index d01c9f927f..b5cb6cdac6 100644 --- a/packages/yoroi-extension/features/step_definitions/uri-steps.js +++ b/packages/yoroi-extension/features/step_definitions/uri-steps.js @@ -5,23 +5,24 @@ import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import { truncateAddress, } from '../../app/utils/formatters'; import { amountInput } from '../pages/walletSendPage'; +import { copyToClipboardIcon, generateUriButton, generateUriIcon, invalidUriDialog, uriDetailsConfirmButton, uriDisplayDialog, uriGenerateDialog, uriLandingDialog, uriLandingDialogAcceptButton, uriVerifyDialog, uriVerifyDialogAddress, uriVerifyDialogAmount } from '../pages/walletReceivePage'; When(/^I click on "generate payment URL" button$/, async function () { - await this.click({ locator: '.WalletReceive_generateURIIcon', method: 'css' }); - await this.waitForElement({ locator: '.URIGenerateDialog', method: 'css' }); + await this.click(generateUriIcon); + await this.waitForElement(uriGenerateDialog); }); Then(/^I generate a URI for ([0-9]+) ADA$/, async function (amount) { await this.input(amountInput, amount); - await this.click({ locator: '.URIGenerateDialog_component .MuiButton-primary', method: 'css' }); + await this.click(generateUriButton); }); Then(/^I should see the URI displayed in a new dialog$/, async function () { - await this.waitForElement({ locator: '.URIDisplayDialog', method: 'css' }); + await this.waitForElement(uriDisplayDialog); }); Then(/^I click on the copy to clipboard icon$/, async function () { - await this.click({ locator: '.URIDisplayDialog_uriDisplay .CopyableAddress_copyIconBig', method: 'css' }); + await this.click(copyToClipboardIcon); }); When(/^I open a cardano URI for address (([^"]*)) and ([0-9]+) ADA$/, async function (address, amount) { @@ -33,27 +34,27 @@ When(/^I open a cardano URI for address (([^"]*)) and ([0-9]+) ADA$/, async func }); Then(/^I should see and accept a warning dialog$/, async function () { - await this.waitForElement({ locator: '.URILandingDialog', method: 'css' }); - await this.click({ locator: '.URILandingDialog .MuiButton-primary', method: 'css' }); + await this.waitForElement(uriLandingDialog); + await this.click(uriLandingDialogAcceptButton); }); Then(/^I should see a dialog with the transaction details$/, async function (table) { const fields = table.hashes()[0]; - await this.waitForElement({ locator: '.URIVerifyDialog', method: 'css' }); - await this.waitUntilContainsText({ locator: '.URIVerifyDialog_address', method: 'css' }, truncateAddress(fields.address)); - await this.waitUntilContainsText({ locator: '.URIVerifyDialog_amount', method: 'css' }, fields.amount); + await this.waitForElement(uriVerifyDialog); + await this.waitUntilContainsText(uriVerifyDialogAddress, truncateAddress(fields.address)); + await this.waitUntilContainsText(uriVerifyDialogAmount, fields.amount); }); When(/^I confirm the URI transaction details$/, async function () { - await this.click({ locator: '.URIVerifyDialog .primary', method: 'css' }); + await this.click(uriDetailsConfirmButton); }); Then(/^I should land on send wallet screen with prefilled parameters$/, async function (table) { const fields = table.hashes()[0]; const rxInput = await this.driver.findElement(By.xpath("//input[@name='receiver']")).getAttribute('value'); expect(rxInput).to.be.equal(fields.address); - const amountInput = await this.driver.findElement(By.xpath("//input[@name='amount']")).getAttribute('value'); - expect(amountInput).to.be.equal(fields.amount); + const sendAmountInput = await this.driver.findElement(By.xpath("//input[@name='amount']")).getAttribute('value'); + expect(sendAmountInput).to.be.equal(fields.amount); }); When(/^I open an invalid cardano URI$/, async function () { @@ -65,5 +66,5 @@ When(/^I open an invalid cardano URI$/, async function () { }); Then(/^I should see an "invalid URI" dialog$/, async function () { - await this.waitForElement({ locator: '.URIInvalidDialog', method: 'css' }); + await this.waitForElement(invalidUriDialog); }); diff --git a/packages/yoroi-extension/features/step_definitions/voting-steps.js b/packages/yoroi-extension/features/step_definitions/voting-steps.js index f1fefab173..d0fd1c934d 100644 --- a/packages/yoroi-extension/features/step_definitions/voting-steps.js +++ b/packages/yoroi-extension/features/step_definitions/voting-steps.js @@ -2,93 +2,111 @@ import { When, Then } from 'cucumber'; import { By, Key } from 'selenium-webdriver'; +import { votingTab } from '../pages/walletPage'; +import { + confirmPinButton, + confirmPinDialog, + confirmPinDialogError, + errorBlock, + generatedPinButton, + generatePinDialog, + pinInput, + qrCodeDialog, + registerButton, + registerDialog, + registerDialogNextButton, + votingRegTxDialog, + votingRegTxDialogError, +} from '../pages/walletVotingPage'; import i18n from '../support/helpers/i18n-helpers'; When(/^I go to the voting page$/, async function () { - await this.click({ locator: '.voting', method: 'css' }); + await this.click(votingTab); }); When(/^I click on the register button in the voting page$/, async function () { - await this.click({ locator: '.Voting_registerButton > .primary', method: 'css' }); + await this.click(registerButton); }); Then(/^I see the Auto generated Pin Steps$/, async function () { const elements = await this.driver.findElements(By.css('.GeneratePinDialog_pin > span ')); const pin = []; - for(const item of elements){ + for (const item of elements) { pin.push(await item.getText()); } this.pin = pin; - await this.waitForElement({ locator: '.GeneratePinDialog_dialog', method: 'css' }); + await this.waitForElement(generatePinDialog); }); When(/^I click next on the generated pin step$/, async function () { - await this.click({ locator: '.GeneratePinDialog_dialog > .Dialog_actions > .primary', method: 'css' }); + await this.click(generatedPinButton); }); Then(/^I see the confirm Pin step$/, async function () { - await this.waitForElement({ locator: '.ConfirmPinDialog_dialog', method: 'css' }); + await this.waitForElement(confirmPinDialog); }); Then(/^I enter the generated pin$/, async function () { const pin = this.pin.join(''); - await this.input({ locator: "input[name='pin']", method: 'css' }, pin); + await this.input(pinInput, pin); }); When(/^I click next on the confirm pin step$/, async function () { - await this.click({ locator: '.ConfirmPinDialog_dialog > .Dialog_actions > .primary', method: 'css' }); + await this.click(confirmPinButton); }); Then(/^I see register step with spending password$/, async function () { - await this.waitForElement({ locator: '.RegisterDialog_dialog', method: 'css' }); + await this.waitForElement(registerDialog); }); When(/^I click next on the register step$/, async function () { - await this.click({ locator: '.RegisterDialog_dialog > .Dialog_actions > .primary', method: 'css' }); + await this.click(registerDialogNextButton); }); Then(/^I see confirm transaction step$/, async function () { - await this.waitForElement({ locator: '.VotingRegTxDialog_dialog', method: 'css' }); + await this.waitForElement(votingRegTxDialog); }); Then(/^Then I see qr code step$/, async function () { - await this.waitForElement({ locator: '.QrCodeDialog_dialog', method: 'css' }); + await this.waitForElement(qrCodeDialog); }); Then(/^I enter the wrong pin$/, async function () { // use copy to avoid reversing original pin const pin = [...this.pin].reverse().join(''); - await this.input({ locator: "input[name='pin']", method: 'css' }, pin); + await this.input(pinInput, pin); }); Then(/^I see should see pin mismatch error$/, async function () { - const errorMessage = await i18n.formatMessage(this.driver, { id: 'global.errors.pinDoesNotMatch' }); + const errorMessage = await i18n.formatMessage(this.driver, { + id: 'global.errors.pinDoesNotMatch', + }); // following selector is used as the error is deeply nested - await this.waitUntilText( - { locator: '.ConfirmPinDialog_dialog .ConfirmPinDialog_pinInputContainer .FormFieldOverridesClassic_error', method: 'css' }, - errorMessage - ); + await this.waitUntilText(confirmPinDialogError, errorMessage); // clear the wrong pin at the end // we doing backspace 4 times for pin length of 4 // as .clear() does not update the react component value const input = this.driver.findElement(By.name('pin')); - for(let i = 1; i<=4; i++) { + for (let i = 1; i <= 4; i++) { input.sendKeys(Key.BACK_SPACE); } }); Then(/^I see incorrect wallet password dialog$/, async function () { - const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.IncorrectPasswordError' }); + const errorMessage = await i18n.formatMessage(this.driver, { + id: 'api.errors.IncorrectPasswordError', + }); - await this.waitUntilText({ locator: '.ErrorBlock_component > span', method: 'css' }, errorMessage); + await this.waitUntilText(errorBlock, errorMessage); }); - Then(/^I see incorrect wallet password error in transaction step$/, async function () { - const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.IncorrectPasswordError' }); + const errorMessage = await i18n.formatMessage(this.driver, { + id: 'api.errors.IncorrectPasswordError', + }); - await this.waitUntilText({ locator: '.VotingRegTxDialog_error', method: 'css' }, errorMessage); + await this.waitUntilText(votingRegTxDialogError, errorMessage); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js index b4b98ab4d6..69f3bd8cae 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js @@ -6,12 +6,26 @@ import i18n from '../support/helpers/i18n-helpers'; import { expect, assert } from 'chai'; import { checkErrorByTranslationId } from './common-steps'; import { + clearButton, + createOptionDialog, + createPaperWalletButton, + createPersonalWalletButton, createWalletButton, + createWalletNameError, + createWalletPasswordError, + createWalletPasswordHelperText, + createWalletPasswordInput, + createWalletRepeatPasswordInput, getCurrencyButton, - pickUpCurrencyDialog + pickUpCurrencyDialog, + recoveryPhraseButton, + recoveryPhraseConfirmButton, + securityWarning, + walletRecoveryPhraseMnemonicComponent, } from '../pages/newWalletPages'; import { continueButton } from '../pages/basicSetupPage'; import { dialogTitle } from '../pages/commonDialogPage'; +import { addAdditionalWalletButton } from '../pages/walletPage'; When(/^I click the create button$/, async function () { await this.click(createWalletButton); @@ -27,35 +41,36 @@ When(/^I select Create Wallet$/, async function () { await this.click(createWalletButton); }); When(/^I select Paper Wallet$/, async function () { - await this.waitForElement({ locator: '.WalletCreateOptionDialog', method: 'css' }); - await this.click({ locator: '.WalletCreateOptionDialog_restorePaperWallet', method: 'css' }); + await this.waitForElement(createOptionDialog); + await this.click(createPaperWalletButton); }); When(/^I enter the created wallet password:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: '.WalletCreateDialog .walletPassword input', method: 'css' }, fields.password); - await this.input({ locator: '.WalletCreateDialog .repeatedPassword input', method: 'css' }, fields.repeatedPassword); + await this.input(createWalletPasswordInput, fields.password); + await this.input(createWalletRepeatPasswordInput, fields.repeatedPassword); }); When(/^I clear the created wallet password ([^"]*)$/, async function (password) { - await this.clearInputUpdatingForm({ locator: '.WalletCreateDialog .walletPassword input', method: 'css' }, password.length); + await this.clearInputUpdatingForm(createWalletPasswordInput, password.length); }); When(/^I click the "Create personal wallet" button$/, async function () { - await this.click({ locator: '.WalletCreateDialog .primary', method: 'css' }); + await this.click(createPersonalWalletButton); }); Then(/^I should see the invalid password error message:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '//p[starts-with(@id, "walletPassword") and contains(@id, "-helper-text")]', method: 'xpath' }, - error); + await checkErrorByTranslationId(this, createWalletPasswordHelperText, error); }); Then(/^I see the submit button is disabled$/, async function () { - const dialogElement = await this.driver.findElement(By.xpath('//div[contains(@class, "Dialog")]')); - const disabledButton = await dialogElement.findElement(By.xpath('.//button[contains(@class, "primary")]')); + const dialogElement = await this.driver.findElement( + By.xpath('//div[contains(@class, "Dialog")]') + ); + const disabledButton = await dialogElement.findElement( + By.xpath('.//button[contains(@class, "primary")]') + ); const buttonState = await disabledButton.isEnabled(); expect(buttonState).to.be.false; }); @@ -72,34 +87,37 @@ When(/^I accept the creation terms$/, async function () { When(/^I copy and enter the displayed mnemonic phrase$/, async function () { const mnemonicElement = await this.waitElementTextMatches( /^.*$/, - { locator: '.WalletRecoveryPhraseMnemonic_component', method: 'css' } + walletRecoveryPhraseMnemonicComponent ); const mnemonic = await mnemonicElement.getText(); - await this.click({ locator: '.WalletRecoveryPhraseDisplayDialog .primary', method: 'css' }); + await this.click(recoveryPhraseButton); const recoveryPhrase = mnemonic.split(' '); for (let i = 0; i < recoveryPhrase.length; i++) { const word = recoveryPhrase[i]; // same word can occur many times, so we look for any copy of the desired word still unselected await this.click({ - locator: "(//button[contains(@class,'MnemonicWord_component') " + // any word + locator: + "(//button[contains(@class,'MnemonicWord_component') " + // any word ` and (text() = '${word}')])`, - method: 'xpath' }); + method: 'xpath', + }); } const checkboxes = await this.driver.findElements( By.xpath("//input[contains(@class,'PrivateSwitchBase-input')]") ); checkboxes.forEach(box => box.click()); - await this.click({ locator: '//button[text()="Confirm"]', method: 'xpath' }); + await this.click(recoveryPhraseConfirmButton); }); When(/^I enter random mnemonic phrase$/, async function () { - await this.click({ locator: '.WalletRecoveryPhraseDisplayDialog .primary', method: 'css' }); + await this.click(recoveryPhraseButton); for (let i = 15; i > 1; i--) { await this.click({ locator: `//div[@class='WalletRecoveryPhraseEntryDialog_words']//button[${i}]`, - method: 'xpath' }); + method: 'xpath', + }); } const words = await this.driver.findElement(By.css('.WalletRecoveryPhraseMnemonic_component')); words @@ -109,11 +127,11 @@ When(/^I enter random mnemonic phrase$/, async function () { }); Then(/^I click Clear button$/, async function () { - await this.click({ locator: "//button[contains(text(), 'Clear')]", method: 'xpath' }); + await this.click(clearButton); }); Then(/^I see All selected words are cleared$/, async function () { - await this.waitUntilText({ locator: '.WalletRecoveryPhraseMnemonic_component', method: 'css' }, '', 5000); + await this.waitUntilText(walletRecoveryPhraseMnemonicComponent, '', 5000); }); Then(/^I should stay in the create wallet dialog$/, async function () { @@ -125,29 +143,20 @@ Then( /^I should see "Wallet name requires at least 1 and at most 40 letters." error message:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '.walletName .MuiFormHelperText-root', method: 'css' }, - error); + await checkErrorByTranslationId(this, createWalletNameError, error); } ); Then(/^I should see "Invalid Password" error message:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '.FormFieldOverridesClassic_error', method: 'css' }, - error); + await checkErrorByTranslationId(this, createWalletPasswordError, error); }); Then(/^I see the security warning prior:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '.MuiFormControlLabel-root', method: 'css' }, - error); + await checkErrorByTranslationId(this, securityWarning, error); }); Then(/^I click to add an additional wallet$/, async function () { - await this.click({ locator: `.NavBar_navbar .NavBar_content .MuiButton-primary`, method: 'css' }); + await this.click(addAdditionalWalletButton); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js index b2d43721f7..6383f02f07 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js @@ -1,34 +1,36 @@ // @flow import { Given, When, Then } from 'cucumber'; +import { delegationDashboardPage, delegationDashboardPageButton, delegationFormNextButton, delegationSuccessPage, delegationTxDialog, poolIdInput, stakePoolTicker } from '../pages/walletDelegationPage'; +import { delegationByIdTab } from '../pages/walletPage'; When(/^I go to the delegation by id screen$/, async function () { - await this.click({ locator: '.cardanoStake', method: 'css' }); + await this.click(delegationByIdTab); }); When(/^I fill the delegation id form:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: "input[name='poolId']", method: 'css' }, fields.stakePoolId); + await this.input(poolIdInput, fields.stakePoolId); }); Then(/^I see the stakepool ticker "([^"]*)"$/, async function (ticker) { - await this.waitUntilText({ locator: '.StakePool_userTitle', method: 'css' }, ticker); + await this.waitUntilText(stakePoolTicker, ticker); }); When(/^I click on the next button in the delegation by id$/, async function () { - await this.click({ locator: '.DelegationSendForm_component .MuiButton-primary', method: 'css' }); + await this.click(delegationFormNextButton); }); When(/^I see the delegation confirmation dialog$/, async function () { - await this.waitForElement({ locator: '.DelegationTxDialog_dialog', method: 'css' }); + await this.waitForElement(delegationTxDialog); }); Given(/^I click on see dashboard$/, async function () { - await this.waitForElement({ locator: '.SuccessPage_component', method: 'css' }); - await this.click({ locator: "//button[contains(text(), 'Dashboard page')]", method: 'xpath' }); + await this.waitForElement(delegationSuccessPage); + await this.click(delegationDashboardPageButton); }); When(/^I should see the dashboard screen$/, async function () { - await this.waitForElement({ locator: '.StakingDashboard_page', method: 'css' }); + await this.waitForElement(delegationDashboardPage); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js index 0b7ebd054b..5c204a9d6a 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js @@ -3,14 +3,15 @@ import { Given, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import { expect } from 'chai'; -import { truncateAddress, } from '../../app/utils/formatters'; +import { truncateAddress } from '../../app/utils/formatters'; import { enterRecoveryPhrase } from '../support/helpers/helpers'; import { primaryButton } from '../pages/commonDialogPage'; +import { paperWalletDialogSelect } from '../pages/newWalletPages'; // ========== Paper wallet ========== Then(/^I open Number of Adddresses selection dropdown$/, async function () { - await this.click({ locator: '.WalletPaperDialog_component .MuiSelect-select', method: 'css' }); + await this.click(paperWalletDialogSelect); }); Then(/^I select 2 addresses$/, async function () { @@ -27,31 +28,33 @@ const fakeAddresses = [ ]; Then(/^I enter the paper recovery phrase$/, async function () { /** - * Mnemomic is printed on the paper wallet and not present in the UI - * So we instead fetch the paper wallet from app memory - */ - const recoveryPhrase = await this.driver.executeScript(() => ( - window.yoroi.stores.substores.ada.paperWallets.paper.scrambledWords - )); + * Mnemomic is printed on the paper wallet and not present in the UI + * So we instead fetch the paper wallet from app memory + */ + const recoveryPhrase = await this.driver.executeScript( + () => window.yoroi.stores.substores.ada.paperWallets.paper.scrambledWords + ); await enterRecoveryPhrase(this, recoveryPhrase.join(' ')); }); Given(/^I swap the paper wallet addresses$/, async function () { // make sure 2 addresses we generated as expected - const addresses = await this.driver.executeScript(() => ( - window.yoroi.stores.substores.ada.paperWallets.paper.addresses - )); + const addresses = await this.driver.executeScript( + () => window.yoroi.stores.substores.ada.paperWallets.paper.addresses + ); expect(addresses.length).to.be.equal(2); // we swap out the generated addresses with fake ones to get a consistent UI for screenshots - await this.driver.executeScript((fakes) => { + await this.driver.executeScript(fakes => { window.yoroi.stores.substores.ada.paperWallets.paper.addresses = fakes; }, fakeAddresses); }); Then(/^I should see two addresses displayed$/, async function () { - const addressesElem = await this.driver.findElements(By.xpath("//span[contains(@class, 'RawHash_hash')]")); + const addressesElem = await this.driver.findElements( + By.xpath("//span[contains(@class, 'RawHash_hash')]") + ); expect(addressesElem.length).to.be.equal(fakeAddresses.length); for (let i = 0; i < fakeAddresses.length; i++) { const address = await addressesElem[i].getText(); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js index e2bea7a8b6..21862664c0 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js @@ -14,14 +14,29 @@ import { paperPasswordInput, recoveryPhraseField, repeatPasswordInput, + restoreWalletButton, walletPasswordInput, } from '../pages/restoreWalletPage'; import { masterKeyInput } from '../pages/walletClaimTransferPage'; -import { pickUpCurrencyDialog, pickUpCurrencyDialogCardano, restoreNormalWallet, shelleyEraButton, walletRestoreDialog, walletRestoreOptionDialog } from '../pages/newWalletPages'; +import { + byronEraButton, + pickUpCurrencyDialog, + pickUpCurrencyDialogCardano, + recoveryPhraseDeleteIcon, + recoveryPhraseError, + restore24WordWallet, + restoreDialogButton, + restoreNormalWallet, + restorePaperWalletButton, + shelleyEraButton, + walletAlreadyExistsComponent, + walletRestoreDialog, + walletRestoreOptionDialog, +} from '../pages/newWalletPages'; import { dialogTitle } from '../pages/commonDialogPage'; When(/^I click the restore button for ([^"]*)$/, async function (currency) { - await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); + await this.click(restoreWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click({ locator: `.PickCurrencyOptionDialog_${currency}`, method: 'css' }); @@ -40,7 +55,7 @@ Then(/^I select Shelley-era 15-word wallet$/, async function () { await this.waitForElement(walletRestoreDialog); }); Then(/^I select Shelley-era 24-word wallet$/, async function () { - await this.click({ locator: '.WalletRestoreOptionDialog_normal24WordWallet', method: 'css' }); + await this.click(restore24WordWallet); await this.waitForElement(walletRestoreDialog); }); @@ -50,14 +65,14 @@ Then(/^I select bip44 15-word wallet$/, async function () { }); When(/^I click the restore paper wallet button$/, async function () { - await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); + await this.click(restoreWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); await this.waitForElement(walletRestoreOptionDialog); - await this.click({ locator: '.WalletRestoreOptionDialog_restorePaperWallet', method: 'css' }); + await this.click(restorePaperWalletButton); await this.waitForElement(walletRestoreDialog); }); @@ -123,7 +138,7 @@ When(/^I clear the restored wallet password ([^"]*)$/, async function (password) }); When(/^I click the "Restore Wallet" button$/, async function () { - await this.click({ locator: '.WalletRestoreDialog .primary', method: 'css' }); + await this.click(restoreDialogButton); }); Then(/^I should see an "Invalid recovery phrase" error message$/, async function () { @@ -157,7 +172,7 @@ Then(/^I should stay in the restore wallet dialog$/, async function () { Then(/^I delete recovery phrase by clicking "x" signs$/, async function () { const webElements = await this.driver.findElements(By.xpath(`//span[contains(text(), '×')]`)); for (let i = 0; i < webElements.length; i++) { - await this.click({ locator: `(//span[contains(text(), '×')])[1]`, method: 'xpath' }); + await this.click(recoveryPhraseDeleteIcon); } const expectedElements = await this.driver.findElements( By.xpath(`//span[contains(text(), '×')]`) @@ -168,10 +183,7 @@ Then(/^I delete recovery phrase by clicking "x" signs$/, async function () { Then(/^I should see an "Invalid recovery phrase" error message:$/, async function (data) { const expectedError = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '//p[starts-with(@id, "recoveryPhrase--")]', method: 'xpath' }, - expectedError); + await checkErrorByTranslationId(this, recoveryPhraseError, expectedError); }); Then(/^I don't see last word of ([^"]*) in recovery phrase field$/, async function (table) { @@ -179,7 +191,7 @@ Then(/^I don't see last word of ([^"]*) in recovery phrase field$/, async functi const lastWord = words[words.length - 1]; await this.waitForElementNotPresent({ locator: `//span[contains(@class, 'SimpleAutocomplete') and contains(text(), "${lastWord}")]`, - method: 'xpath' + method: 'xpath', }); }); @@ -191,15 +203,11 @@ Then(/^I should see an "(\d{1,2}) words left" error message:$/, async function ( id: expectedError.message, values: { number: Number(number) }, }); - const errorSelector = '//p[starts-with(@id, "recoveryPhrase--")]'; - await this.waitUntilText( - { locator: errorSelector, method: 'xpath' }, - errorMessage, - 15000); + await this.waitUntilText(recoveryPhraseError, errorMessage, 15000); }); Then(/^I should see the wallet already exist window$/, async function () { - await this.waitForElement({ locator: '.WalletAlreadyExistDialog_component', method: 'css' }); + await this.waitForElement(walletAlreadyExistsComponent); }); When(/^I click the Open wallet button$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/wallet-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-steps.js index fc21b063ff..add95d7745 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-steps.js @@ -2,8 +2,10 @@ import { When, Then } from 'cucumber'; import { truncateLongName } from '../../app/utils/formatters'; +import { myWalletsPage } from '../pages/mainWindowPage'; import { walletNameInput } from '../pages/restoreWalletPage'; -import { walletNameText } from '../pages/walletPage'; +import { walletNameText, walletNavBackButton } from '../pages/walletPage'; +import { walletButtonClassic } from '../pages/sidebarPage'; When(/^I enter the name "([^"]*)"$/, async function (walletName) { await this.input(walletNameInput, walletName); @@ -14,7 +16,7 @@ When(/^I clear the name "([^"]*)"$/, async function (walletName) { }); When(/^I navigate to wallet sidebar category$/, async function () { - await this.click({ locator: `//div[@class='Sidebar_categories']//button[1]`, method: 'xpath' }); + await this.click(walletButtonClassic); await this.waitForElement(walletNameText); }); @@ -23,9 +25,9 @@ Then(/^I should see the opened wallet with name "([^"]*)"$/, async function (wal }); Then(/^I unselect the wallet$/, async function () { - await this.click({ locator: '.NavBar_navbar .NavBar_title .NavBarBack_backButton', method: 'css' }); + await this.click(walletNavBackButton); }); When(/^I am on the my wallets screen$/, async function () { - await this.waitForElement({ locator: '.MyWallets_page', method: 'css' }); + await this.waitForElement(myWalletsPage); }); diff --git a/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js b/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js index 829898338e..66e210e54a 100644 --- a/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js +++ b/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js @@ -2,10 +2,7 @@ import { Given, When, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; -import { - navigateTo, - waitUntilUrlEquals -} from '../support/helpers/route-helpers'; +import { navigateTo, waitUntilUrlEquals } from '../support/helpers/route-helpers'; import i18n from '../support/helpers/i18n-helpers'; import { checkAddressesRecoveredAreCorrect, @@ -13,13 +10,39 @@ import { checkWithdrawalAddressesRecoveredAreCorrect, } from '../support/helpers/transfer-helpers'; import { claimTransferTab } from '../pages/walletPage'; -import { byronButton } from '../pages/walletClaimTransferPage'; +import { + byronButton, + cancelTransferButton, + descryptionPasswordInput, + icarusTab, + keyInput, + shelleyPrivateKeyInput, + restore15WordWalletIcarus, + restoreShelley15WordDialog, + restoreShelleyPaperWalletDialog, + shelleyEraCard, + restoreIcarusPaperWalletOption, + transferSummaryPage, + trezorOption, + ledgerOption, + yoroiPaperButton, + transferErrorPageTitle, + transferButton, + transferSuccessPageTitle, + createYoroiWalletButton, + transferSummaryPageError, + keepRegisteredButton, + transferSummaryRefundText, +} from '../pages/walletClaimTransferPage'; import { primaryButton } from '../pages/commonDialogPage'; +import { fullScreenMessage } from '../pages/settingsPage'; -async function confirmAttentionScreen(customWorld: Object){ +async function confirmAttentionScreen(customWorld: Object) { // Attention screen await customWorld.waitForElement({ locator: '.HardwareDisclaimer_component', method: 'css' }); - const disclaimerClassElement = await customWorld.driver.findElement(By.css('.HardwareDisclaimer_component')); + const disclaimerClassElement = await customWorld.driver.findElement( + By.css('.HardwareDisclaimer_component') + ); const checkbox = await disclaimerClassElement.findElement(By.xpath('//input[@type="checkbox"]')); await checkbox.click(); await customWorld.click({ locator: '//button[text()="I understand"]', method: 'xpath' }); @@ -35,63 +58,64 @@ Given(/^Revamp. I go to the claim\/transfer page$/, async function () { }); When(/^I click skip the transfer$/, async function () { - await this.click({ locator: '.cancelTransferButton', method: 'css' }); + await this.click(cancelTransferButton); }); When(/^I click on the shelley button on the transfer screen$/, async function () { - await this.click({ locator: '.TransferCards_shelleyEra', method: 'css' }); + await this.click(shelleyEraCard); }); When(/^I click on the byron button on the transfer screen$/, async function () { await this.click(byronButton); }); Then(/^I click on the icarus tab$/, async function () { - await this.click({ locator: '.IcarusTab', method: 'css' }); + await this.click(icarusTab); }); Then(/^I select the Byron 15-word option$/, async function () { - await this.click({ locator: '.fromIcarusWallet15Word_restoreNormalWallet', method: 'css' }); + await this.click(restore15WordWalletIcarus); }); Then(/^I select the Shelley 15-word option$/, async function () { - await this.click({ locator: '.ShelleyOptionDialog_restoreNormalWallet', method: 'css' }); + await this.click(restoreShelley15WordDialog); }); Then(/^I select the Shelley paper wallet option$/, async function () { - await this.click({ locator: '.ShelleyOptionDialog_restorePaperWallet', method: 'css' }); + await this.click(restoreShelleyPaperWalletDialog); }); When(/^I enter the key "([^"]*)"$/, async function (password) { - await this.input({ locator: "input[name='key']", method: 'css' }, password); + await this.input(keyInput, password); }); When(/^I enter the decryption password "([^"]*)"$/, async function (password) { - await this.input({ locator: "input[name='decryptionPassword']", method: 'css' }, password); + await this.input(descryptionPasswordInput, password); }); Then(/^I select the private key option$/, async function () { - await this.click({ locator: '.ShelleyOptionDialog_masterKey', method: 'css' }); + await this.click(shelleyPrivateKeyInput); }); Then(/^I select the yoroi paper wallet option$/, async function () { - await this.click({ locator: '.fromIcarusPaperWallet_restorePaperWallet', method: 'css' }); + await this.click(restoreIcarusPaperWalletOption); }); Then(/^I see the transfer transaction$/, async function () { - await this.waitForElement({ locator: '.TransferSummaryPage_body', method: 'css' }); + await this.waitForElement(transferSummaryPage); }); Then(/^I accept the prompt$/, async function () { await this.click(primaryButton); }); Then(/^I select the trezor option$/, async function () { - await this.click({ locator: '.fromTrezor_connectTrezor', method: 'css' }); + await this.click(trezorOption); // Attention screen await confirmAttentionScreen(this); }); Then(/^I select the ledger option$/, async function () { - await this.click({ locator: '.fromLedger_connectLedger', method: 'css' }); + await this.click(ledgerOption); // Attention screen await confirmAttentionScreen(this); }); When(/^I click on the yoroiPaper button on the Yoroi Transfer start screen$/, async function () { - await this.click({ locator: '.yoroiPaper', method: 'css' }); + await this.click(yoroiPaperButton); }); Then(/^I should see the Yoroi transfer error screen$/, async function () { - const errorPageTitle = await i18n.formatMessage(this.driver, - { id: 'api.errors.generateTransferTxError' }); - await this.waitUntilText({ locator: '.ErrorPage_title', method: 'css' }, errorPageTitle); + const errorPageTitle = await i18n.formatMessage(this.driver, { + id: 'api.errors.generateTransferTxError', + }); + await this.waitUntilText(transferErrorPageTitle, errorPageTitle); }); Then(/^I should see on the Yoroi transfer summary screen:$/, async function (table) { @@ -109,41 +133,40 @@ Then(/^I should see on the Yoroi withdrawal transfer summary screen:$/, async fu }); When(/^I confirm Yoroi transfer funds$/, async function () { - await this.click({ locator: '.transferButton', method: 'css' }); + await this.click(transferButton); }); Then(/^I should see the Yoroi transfer success screen$/, async function () { - const successPageTitle = await i18n.formatMessage(this.driver, - { id: 'yoroiTransfer.successPage.title' }); - await this.waitUntilText({ locator: '.SuccessPage_title', method: 'css' }, successPageTitle.toUpperCase()); + const successPageTitle = await i18n.formatMessage(this.driver, { + id: 'yoroiTransfer.successPage.title', + }); + await this.waitUntilText(transferSuccessPageTitle, successPageTitle.toUpperCase()); }); Then(/^I should see the transfer screen disabled$/, async function () { - const noWalletMessage = await i18n.formatMessage( - this.driver, - { id: 'wallet.nowallet.title' } - ); - await this.waitUntilText({ locator: '.FullscreenMessage_title', method: 'css' }, noWalletMessage); + const noWalletMessage = await i18n.formatMessage(this.driver, { id: 'wallet.nowallet.title' }); + await this.waitUntilText(fullScreenMessage, noWalletMessage); }); Then(/^I should see the "CREATE YOROI WALLET" button disabled$/, async function () { - await this.waitDisable({ locator: '.createYoroiWallet.YoroiTransferStartPage_button', method: 'css' }); + await this.waitDisable(createYoroiWalletButton); }); Then(/^I should see wallet changed notice$/, async function () { - const walletChangedError = await i18n.formatMessage(this.driver, - { id: 'yoroiTransfer.error.walletChangedError' }); - await this.waitUntilText({ locator: '.TransferSummaryPage_error', method: 'css' }, walletChangedError); + const walletChangedError = await i18n.formatMessage(this.driver, { + id: 'yoroiTransfer.error.walletChangedError', + }); + await this.waitUntilText(transferSummaryPageError, walletChangedError); }); When(/^I keep the staking key$/, async function () { - await this.click({ locator: `//button[contains(text(), "Keep registered")]`, method: 'xpath' }); + await this.click(keepRegisteredButton); }); Then(/^I see the deregistration for the transaction$/, async function () { - await this.waitForElement({ locator: '.TransferSummaryPage_refund', method: 'css' }); + await this.waitForElement(transferSummaryRefundText); }); Then(/^I do not see the deregistration for the transaction$/, async function () { - await this.waitForElementNotPresent({ locator: '.TransferSummaryPage_refund', method: 'css' }); + await this.waitForElementNotPresent(transferSummaryRefundText); }); From 4398fcffe41fcc60c0c6ca54d100aab33467d3ac Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Fri, 26 Aug 2022 12:58:12 -0300 Subject: [PATCH 064/199] Added first batch of locators to page objects --- .../features/pages/basicSetupPage.js | 13 ++ .../features/pages/daedalusTransferPage.js | 8 ++ .../features/pages/mainWindowPage.js | 5 + .../features/pages/newWalletPages.js | 5 + .../features/pages/restoreWalletPage.js | 5 +- .../features/pages/settingsPage.js | 15 +++ .../features/pages/uriPromptPage.js | 8 ++ .../features/pages/walletDashboardPage.js | 9 ++ .../features/pages/walletPage.js | 6 +- .../features/pages/walletReceivePage.js | 11 +- .../addresses-generation-steps.js | 24 ++-- .../features/step_definitions/common-steps.js | 125 ++++++++++-------- .../daedalus-transfer-steps.js | 15 ++- .../step_definitions/dashboard-steps.js | 12 +- .../general-settings-steps.js | 16 +-- .../step_definitions/hardware-steps.js | 23 ++-- .../step_definitions/select-language-steps.js | 3 +- .../step_definitions/settings-ui-steps.js | 3 +- .../step_definitions/wallet-creation-steps.js | 3 +- .../wallet-restoration-steps.js | 31 ++--- .../features/step_definitions/wallet-steps.js | 5 +- .../helpers/language-selection-helpers.js | 5 +- 22 files changed, 224 insertions(+), 126 deletions(-) create mode 100644 packages/yoroi-extension/features/pages/basicSetupPage.js create mode 100644 packages/yoroi-extension/features/pages/daedalusTransferPage.js create mode 100644 packages/yoroi-extension/features/pages/mainWindowPage.js create mode 100644 packages/yoroi-extension/features/pages/settingsPage.js create mode 100644 packages/yoroi-extension/features/pages/uriPromptPage.js create mode 100644 packages/yoroi-extension/features/pages/walletDashboardPage.js diff --git a/packages/yoroi-extension/features/pages/basicSetupPage.js b/packages/yoroi-extension/features/pages/basicSetupPage.js new file mode 100644 index 0000000000..7cf2f79b09 --- /dev/null +++ b/packages/yoroi-extension/features/pages/basicSetupPage.js @@ -0,0 +1,13 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +// language select page +export const languageSelectionForm: LocatorObject = { locator: '.LanguageSelectionForm_component', method: 'css' }; + + +export const continueButton: LocatorObject = { locator: '//button[text()="Continue"]', method: 'xpath' }; +// ToS page +export const termsOfUseComponent: LocatorObject = { locator: '.TermsOfUseForm_component', method: 'css' }; +// uri prompt page +export const walletAddComponent: LocatorObject ={ locator: '.WalletAdd_component', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/daedalusTransferPage.js b/packages/yoroi-extension/features/pages/daedalusTransferPage.js new file mode 100644 index 0000000000..f04a4cfc80 --- /dev/null +++ b/packages/yoroi-extension/features/pages/daedalusTransferPage.js @@ -0,0 +1,8 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const nextButton: LocatorObject = { locator: "//button[contains(@label, 'Next')]", method: 'xpath' }; +export const backButton: LocatorObject = { locator: "//button[contains(@label, 'Back')]", method: 'xpath' }; +export const formFieldOverridesClassicError: LocatorObject = { locator: '.FormFieldOverridesClassic_error', method: 'css' }; +export const transferButton: LocatorObject = { locator: '.transferButton', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/mainWindowPage.js b/packages/yoroi-extension/features/pages/mainWindowPage.js new file mode 100644 index 0000000000..d80f6e1609 --- /dev/null +++ b/packages/yoroi-extension/features/pages/mainWindowPage.js @@ -0,0 +1,5 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const yoroiClassic: LocatorObject = { locator: '.YoroiClassic', method: 'css' } \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/newWalletPages.js b/packages/yoroi-extension/features/pages/newWalletPages.js index 27a5240574..473a61f47f 100644 --- a/packages/yoroi-extension/features/pages/newWalletPages.js +++ b/packages/yoroi-extension/features/pages/newWalletPages.js @@ -7,6 +7,11 @@ export const createWalletButton: LocatorObject = { locator: '.WalletAdd_btnCreat export const restoreWalletButton: LocatorObject = { locator: '.WalletAdd_btnRestoreWallet', method: 'css' }; // Currency options dialog export const pickUpCurrencyDialog: LocatorObject = { locator: '.PickCurrencyOptionDialog', method: 'css' }; +export const pickUpCurrencyDialogErgo: LocatorObject = { locator: '.PickCurrencyOptionDialog_ergo', method: 'css' }; +export const pickUpCurrencyDialogCardano: LocatorObject = { locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }; +export const walletRestoreOptionDialog = { locator: '.WalletRestoreOptionDialog', method: 'css' }; +export const restoreNormalWallet = { locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }; +export const walletRestoreDialog = { locator: '.WalletRestoreDialog', method: 'css' }; export const getCurrencyButton = (currency: string): LocatorObject => { return { locator: `.PickCurrencyOptionDialog_${currency}`, method: 'css' }; }; diff --git a/packages/yoroi-extension/features/pages/restoreWalletPage.js b/packages/yoroi-extension/features/pages/restoreWalletPage.js index 1763940354..b2a43a4fb7 100644 --- a/packages/yoroi-extension/features/pages/restoreWalletPage.js +++ b/packages/yoroi-extension/features/pages/restoreWalletPage.js @@ -26,6 +26,9 @@ export const getWords = (word: string): LocatorObject => { return { locator: `//span[contains(text(), '${word}')]`, method: 'xpath' } }; +export const walletNameInput = { locator: "input[name='walletName']", method: 'css' }; +export const restoreWalletButton = { locator: '.WalletRestoreDialog .primary', method: 'css' } export const walletPasswordInput = { locator: "input[name='walletPassword']", method: 'css' }; export const repeatPasswordInput = { locator: "input[name='repeatPassword']", method: 'css' }; -export const paperPasswordInput = { locator: "input[name='paperPassword']", method: 'css' }; \ No newline at end of file +export const paperPasswordInput = { locator: "input[name='paperPassword']", method: 'css' }; +export const confirmButton = { locator: '.confirmButton', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/settingsPage.js b/packages/yoroi-extension/features/pages/settingsPage.js new file mode 100644 index 0000000000..2ec9e1b36f --- /dev/null +++ b/packages/yoroi-extension/features/pages/settingsPage.js @@ -0,0 +1,15 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const settingsLayoutComponent: LocatorObject = { locator: '.SettingsLayout_component', method: 'css' }; +export const secondThemeSelected: LocatorObject = { + locator: '.ThemeSettingsBlock_themesWrapper button:nth-child(2).ThemeSettingsBlock_active', + method: 'css' + }; + + export const complexityLevelForm: LocatorObject = { locator: '.ComplexityLevelForm_cardsWrapper', method: 'css' }; + export const complexitySelected: LocatorObject = { locator: '.currentLevel', method: 'css' }; + export const languageSelector: LocatorObject = { locator: '//div[starts-with(@id, "languageId")]', method: 'xpath' }; + + diff --git a/packages/yoroi-extension/features/pages/uriPromptPage.js b/packages/yoroi-extension/features/pages/uriPromptPage.js new file mode 100644 index 0000000000..88de4cdbe8 --- /dev/null +++ b/packages/yoroi-extension/features/pages/uriPromptPage.js @@ -0,0 +1,8 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const uriPromptForm: LocatorObject = { locator: '.UriPromptForm_component', method: 'css' }; +export const allowButton: LocatorObject = { locator: '.allowButton', method: 'css' }; +export const uriAcceptComponent: LocatorObject = { locator: '.UriAccept_component', method: 'css' }; +export const finishButton: LocatorObject = { locator: '.finishButton', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/walletDashboardPage.js b/packages/yoroi-extension/features/pages/walletDashboardPage.js new file mode 100644 index 0000000000..aa61194807 --- /dev/null +++ b/packages/yoroi-extension/features/pages/walletDashboardPage.js @@ -0,0 +1,9 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const withdrawButton: LocatorObject = { locator: '.withdrawButton', method: 'css' }; +export const rechartBar: LocatorObject = { locator: '.recharts-bar', method: 'css' }; + +export const mangledWarningIcon: LocatorObject = { locator: '.UserSummary_mangledWarningIcon', method: 'css' }; +export const userSummaryLink: LocatorObject = { locator: '.UserSummary_link', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletPage.js b/packages/yoroi-extension/features/pages/walletPage.js index 4f80c90d2c..e1a0f7bb8c 100644 --- a/packages/yoroi-extension/features/pages/walletPage.js +++ b/packages/yoroi-extension/features/pages/walletPage.js @@ -4,4 +4,8 @@ export const summaryTab = { locator: 'summary', method: 'css' }; export const sendTab = { locator: '.send', method: 'css' }; export const receiveTab = { locator: '.receive', method: 'css' }; -export const claimTransferTab = { locator: '.claimTransfer', method: 'css' }; \ No newline at end of file +export const claimTransferTab = { locator: '.claimTransfer', method: 'css' }; + +export const walletNameText = { locator: '.NavPlate_name', method: 'css' }; +export const activeNavTab = { locator: '.WalletNavButton_active', method: 'css' }; +export const dashboardTab = { locator: '.stakeDashboard ', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/walletReceivePage.js b/packages/yoroi-extension/features/pages/walletReceivePage.js index 6ad8a1cccc..387bb35067 100644 --- a/packages/yoroi-extension/features/pages/walletReceivePage.js +++ b/packages/yoroi-extension/features/pages/walletReceivePage.js @@ -5,16 +5,19 @@ import type { LocatorObject } from '../support/webdriver'; export const getGeneratedAddressLocator = (rowIndex: number): LocatorObject => { return { locator: `.generatedAddress-${rowIndex + 1} .RawHash_hash`, - method: 'css' + method: 'css', }; }; export const getAddressLocator = (address: string): LocatorObject => { return { locator: `//div[contains(text(), "${address}")]`, - method: 'xpath' + method: 'xpath', }; -} +}; export const addressErrorPhrase = { locator: '.StandardHeader_error', method: 'css' }; -export const generateAddressButton = { locator: '.generateAddressButton', method: 'css' }; \ No newline at end of file +export const generateAddressButton = { locator: '.generateAddressButton', method: 'css' }; +export const addressBookTab = { locator: `.addressBook`, method: 'css' }; +export const rewardAddressTab = { locator: `.reward`, method: 'css' }; +export const yourWalletAddrHeader = { locator: '.StandardHeader_copyableHash', method: 'css' }; diff --git a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js index 7a738f6b87..eff4c477ac 100644 --- a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js @@ -8,9 +8,13 @@ import { checkIfElementsInArrayAreUnique } from '../support/helpers/helpers'; import { truncateAddress, truncateAddressShort } from '../../app/utils/formatters'; import { receiveTab } from '../pages/walletPage'; import { - addressErrorPhrase, generateAddressButton, + addressErrorPhrase, + generateAddressButton, getAddressLocator, getGeneratedAddressLocator, + addressBookTab, + rewardAddressTab, + yourWalletAddrHeader } from '../pages/walletReceivePage'; Given(/^Revamp. I go to the receive screen$/, async function () { @@ -18,7 +22,7 @@ Given(/^Revamp. I go to the receive screen$/, async function () { }); Given(/^I go to the receive screen$/, async function () { - await this.click({ locator: '.receive', method: 'css' }); + await this.click(receiveTab); }); When(/^I click on the Generate new address button$/, async function () { @@ -30,17 +34,11 @@ When(/^I click on the ([^ ]*) ([^ ]*) tab$/, async function (kind, chain) { }); When(/^I click on the top-level address book tab$/, async function () { - await this.click({ - locator: `//div[contains(text(), "Address book") and contains(@class, "ReceiveNavButton_label")]`, - method: 'xpath', - }); + await this.click(addressBookTab); }); When(/^I click on the top-level reward address tab$/, async function () { - await this.click({ - locator: `//div[contains(text(), "Reward") and contains(@class, "ReceiveNavButton_label")]`, - method: 'xpath', - }); + await this.click(rewardAddressTab); }); When(/^I click on the All addresses button$/, async function () { @@ -70,7 +68,7 @@ When(/^I click on the HasBalance addresses button$/, async function () { When(/^I click on the Generate new address button ([0-9]+) times$/, async function (times) { for (let curr = 1; curr <= times; curr++) { - await this.click({ locator: '.generateAddressButton', method: 'css' }); + await this.click(generateAddressButton); await this.waitForElement({ locator: `.generatedAddress-${curr + 1} .RawHash_hash`, method: 'css', @@ -80,14 +78,16 @@ When(/^I click on the Generate new address button ([0-9]+) times$/, async functi Then(/^I should see my latest address "([^"]*)" at the top$/, async function (address) { await this.waitUntilText( - { locator: '.StandardHeader_copyableHash', method: 'css' }, + yourWalletAddrHeader, truncateAddress(address) ); }); + Then(/^I should see at least ([^"]*) addresses$/, async function (numAddresses) { const rows = await this.driver.findElements(By.css('.WalletReceive_walletAddress')); expect(rows.length).be.at.least(Number.parseInt(numAddresses, 10)); }); + Then( /^I should see ([^"]*) addresses with address "([^"]*)" at the top$/, async function (numAddresses, address) { diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 8d893e70ac..ba4da5c809 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -17,10 +17,7 @@ import { enterRecoveryPhrase, getLogDate } from '../support/helpers/helpers'; import { testWallets } from '../mock-chain/TestWallets'; import * as ErgoImporter from '../mock-chain/mockErgoImporter'; import * as CardanoImporter from '../mock-chain/mockCardanoImporter'; -import { - testRunsDataDir, - snapshotsDir, - } from '../support/helpers/common-constants'; +import { testRunsDataDir, snapshotsDir } from '../support/helpers/common-constants'; import { expect } from 'chai'; import { satisfies } from 'semver'; // eslint-disable-next-line import/named @@ -50,12 +47,39 @@ import { trezorConfirmButton, walletNameInput, saveDialog, - saveButton + saveButton, + pickUpCurrencyDialogErgo, + walletRestoreOptionDialog, + restoreNormalWallet, + walletRestoreDialog, + pickUpCurrencyDialogCardano, + byronEraButton, } from '../pages/newWalletPages'; import { allowPubKeysAndSwitchToYoroi, switchToTrezorAndAllow } from './trezor-steps'; import * as helpers from '../support/helpers/helpers'; import { extensionTabName } from '../support/windowManager'; +import { + confirmButton, + repeatPasswordInput, + walletPasswordInput, +} from '../pages/restoreWalletPage'; +import { walletNameText } from '../pages/walletPage'; +import { + continueButton, + languageSelectionForm, + termsOfUseComponent, + walletAddComponent, +} from '../pages/basicSetupPage'; +import { settingsLayoutComponent } from '../pages/settingsPage'; +import { + allowButton, + finishButton, + uriAcceptComponent, + uriPromptForm, +} from '../pages/uriPromptPage'; +import { yoroiClassic } from '../pages/mainWindowPage'; + const { promisify } = require('util'); const fs = require('fs'); const rimraf = require('rimraf'); @@ -157,7 +181,7 @@ After(async function (scenario) { await getLogs(this.driver, 'failedStep', logging.Type.BROWSER); await getLogs(this.driver, 'failedStep', logging.Type.DRIVER); } - }; + } await this.windowManager.switchTo(extensionTabName); await this.driver.quit(); await helpers.sleep(500); @@ -261,27 +285,18 @@ async function inputMnemonicForWallet( walletName: string, restoreInfo: RestorationInput ): Promise { - await customWorld.input({ locator: "input[name='walletName']", method: 'css' }, restoreInfo.name); + await customWorld.input(walletNameInput, restoreInfo.name); await enterRecoveryPhrase(customWorld, restoreInfo.mnemonic); - await customWorld.input( - { locator: "input[name='walletPassword']", method: 'css' }, - restoreInfo.password - ); - await customWorld.input( - { locator: "input[name='repeatPassword']", method: 'css' }, - restoreInfo.password - ); - await customWorld.click({ locator: '.WalletRestoreDialog .primary', method: 'css' }); + await customWorld.input(walletPasswordInput, restoreInfo.password); + await customWorld.input(repeatPasswordInput, restoreInfo.password); + await customWorld.click(restoreWalletButton); const plateElements = await getPlates(customWorld); const plateText = await plateElements[0].getText(); expect(plateText).to.be.equal(restoreInfo.plate); - await customWorld.click({ locator: '.confirmButton', method: 'css' }); - await customWorld.waitUntilText( - { locator: '.NavPlate_name', method: 'css' }, - truncateLongName(walletName) - ); + await customWorld.click(confirmButton); + await customWorld.waitUntilText(walletNameText, truncateLongName(walletName)); } export async function checkErrorByTranslationId( @@ -302,15 +317,15 @@ Given(/^There is an Ergo wallet stored named ([^"]*)$/, async function (walletNa const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); - await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); + await this.click(restoreWalletButton); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_ergo', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogErgo); - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); + await this.waitForElement(walletRestoreOptionDialog); - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.waitForElement(walletRestoreDialog); await inputMnemonicForWallet(this, walletName, restoreInfo); }); @@ -320,16 +335,16 @@ Given(/^There is a Shelley wallet stored named ([^"]*)$/, async function (wallet const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); - await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); + await this.click(restoreWalletButton); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); + await this.waitForElement(walletRestoreOptionDialog); - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.click(shelleyEraButton); + await this.waitForElement(walletRestoreDialog); await inputMnemonicForWallet(this, walletName, restoreInfo); }); @@ -344,11 +359,11 @@ Given(/^There is a Byron wallet stored named ([^"]*)$/, async function (walletNa await this.waitForElement(pickUpCurrencyDialog); await this.click(getCurrencyButton('cardano')); - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); + await this.waitForElement(walletRestoreOptionDialog); - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.click(byronEraButton); + await this.waitForElement(walletRestoreDialog); await inputMnemonicForWallet(this, walletName, restoreInfo); }); @@ -356,17 +371,17 @@ Given(/^There is a Byron wallet stored named ([^"]*)$/, async function (walletNa Given(/^I have completed the basic setup$/, async function () { this.webDriverLogger.info(`Step: I have completed the basic setup`); // language select page - await this.waitForElement({ locator: '.LanguageSelectionForm_component', method: 'css' }); - await this.click({ locator: '//button[text()="Continue"]', method: 'xpath' }); + await this.waitForElement(languageSelectionForm); + await this.click(); // ToS page - await this.waitForElement({ locator: '.TermsOfUseForm_component', method: 'css' }); + await this.waitForElement(termsOfUseComponent); const tosClassElement = await this.driver.findElement(By.css('.TermsOfUseForm_component')); const checkbox = await tosClassElement.findElement(By.xpath('//input[@type="checkbox"]')); await checkbox.click(); - await this.click({ locator: '//button[text()="Continue"]', method: 'xpath' }); + await this.click(continueButton); // uri prompt page await acceptUriPrompt(this); - await this.waitForElement({ locator: '.WalletAdd_component', method: 'css' }); + await this.waitForElement(walletAddComponent); }); Given(/^I switch to the advanced level$/, async function () { @@ -375,7 +390,7 @@ Given(/^I switch to the advanced level$/, async function () { await navigateTo.call(this, '/settings'); await navigateTo.call(this, '/settings/general'); await waitUntilUrlEquals.call(this, '/settings/general'); - await this.waitForElement({ locator: '.SettingsLayout_component', method: 'css' }); + await this.waitForElement(settingsLayoutComponent); // Click on secondary menu "levelOfComplexity" item await selectSubmenuSettings(this, 'levelOfComplexity'); // Select the most complex level @@ -388,7 +403,7 @@ Given(/^I navigate back to the main page$/, async function () { // Navigate back to the main page await navigateTo.call(this, '/wallets/add'); await waitUntilUrlEquals.call(this, '/wallets/add'); - await this.waitForElement({ locator: '.WalletAdd_component', method: 'css' }); + await this.waitForElement(walletAddComponent); }); Then(/^I accept uri registration$/, async function () { @@ -398,10 +413,10 @@ Then(/^I accept uri registration$/, async function () { async function acceptUriPrompt(world: any) { if (world.getBrowser() !== 'firefox') { - await world.waitForElement({ locator: '.UriPromptForm_component', method: 'css' }); - await world.click({ locator: '.allowButton', method: 'css' }); - await world.waitForElement({ locator: '.UriAccept_component', method: 'css' }); - await world.click({ locator: '.finishButton', method: 'css' }); + await world.waitForElement(uriPromptForm); + await world.click(allowButton); + await world.waitForElement(uriAcceptComponent); + await world.click(finishButton); } } @@ -419,7 +434,7 @@ Given(/^I refresh the page$/, async function () { await this.driver.navigate().refresh(); // wait for page to refresh await this.driver.sleep(500); - await this.waitForElement({ locator: '.YoroiClassic', method: 'css' }); + await this.waitForElement(yoroiClassic); }); Given(/^I restart the browser$/, async function () { @@ -428,13 +443,13 @@ Given(/^I restart the browser$/, async function () { await this.driver.navigate().refresh(); // wait for page to refresh await this.driver.sleep(500); - await this.waitForElement({ locator: '.YoroiClassic', method: 'css' }); + await this.waitForElement(yoroiClassic); }); Given(/^There is no wallet stored$/, async function () { this.webDriverLogger.info(`Step: There is no wallet stored`); await restoreWalletsFromStorage(this); - await this.waitForElement({ locator: '.WalletAdd_component', method: 'css' }); + await this.waitForElement(walletAddComponent); }); Then(/^I click then button labeled (.*)$/, async function (buttonName) { @@ -455,7 +470,7 @@ Given(/^I import a snapshot named ([^"]*)$/, async function (snapshotName) { await this.driver.navigate().refresh(); // wait for page to refresh await this.driver.sleep(1500); - await this.waitForElement({ locator: '.YoroiClassic', method: 'css' }); + await this.waitForElement(yoroiClassic); }); async function setLedgerWallet(client, serial) { @@ -666,7 +681,7 @@ Then(/^Revamp. I go to the wallet ([^"]*)$/, async function (walletName) { await walletButtonInRow.click(); }); -Then(/^Debug. Take screenshot$/, async function () { +Then(/^Debug. Take screenshot$/, async function () { const currentTime = getLogDate(); await takeScreenshot(this.driver, `debug_${currentTime}`); await takePageSnapshot(this.driver, `debug_${currentTime}`); @@ -676,4 +691,4 @@ Then(/^Debug. Take screenshot$/, async function () { Then(/^Debug. Make driver sleep for 2 seconds$/, async function () { await this.driver.sleep(2000); -}); \ No newline at end of file +}); diff --git a/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js b/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js index 39e032076a..8f51dfcb88 100644 --- a/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js +++ b/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js @@ -20,6 +20,9 @@ import { daedalusMasterKeyButton, twelveWordOption } from '../pages/walletClaimT import { proceedRecoveryButton } from '../pages/restoreWalletPage'; import { errorMessage, errorPageTitle } from '../pages/errorPage'; import { amountField, feeField, totalAmountField } from '../pages/confirmTransactionPage'; +import { walletAddComponent } from '../pages/basicSetupPage'; +import { backButton, formFieldOverridesClassicError, nextButton, transferButton } from '../pages/daedalusTransferPage'; +import { activeNavTab } from '../pages/walletPage'; Before({ tags: '@withWebSocketConnection' }, () => { closeMockServer(); @@ -62,33 +65,33 @@ When(/^I proceed with the recovery$/, async function () { }); When(/^I click next button on the Daedalus transfer page$/, async function () { - await this.click({ locator: "//button[contains(@label, 'Next')]", method: 'xpath' }); + await this.click(nextButton); }); When(/^I click the back button$/, async function () { - await this.click({ locator: "//button[contains(@label, 'Back')]", method: 'xpath' }); + await this.click(backButton); }); Then(/^I should see "This field is required." error message:$/, async function (data) { const error = data.hashes()[0]; await checkErrorByTranslationId( this, - { locator: '.FormFieldOverridesClassic_error', method: 'css' }, + formFieldOverridesClassicError, error); }); When(/^I confirm Daedalus transfer funds$/, async function () { - await this.click({ locator: '.transferButton', method: 'css' }); + await this.click(transferButton); }); Then(/^I should see the Create wallet screen$/, async function () { - await this.waitForElement({ locator: '.WalletAdd_component', method: 'css' }); + await this.waitForElement(walletAddComponent); }); Then(/^I should see the Receive screen$/, async function () { const receiveTitle = await i18n.formatMessage(this.driver, { id: 'wallet.navigation.receive' }); - await this.waitUntilText({ locator: '.WalletNavButton_active', method: 'css' }, receiveTitle); + await this.waitUntilText(activeNavTab, receiveTitle); await this.driver.sleep(2000); }); diff --git a/packages/yoroi-extension/features/step_definitions/dashboard-steps.js b/packages/yoroi-extension/features/step_definitions/dashboard-steps.js index 3dd63b753e..162ec3f647 100644 --- a/packages/yoroi-extension/features/step_definitions/dashboard-steps.js +++ b/packages/yoroi-extension/features/step_definitions/dashboard-steps.js @@ -1,20 +1,22 @@ // @flow import { Then, When, } from 'cucumber'; +import { mangledWarningIcon, rechartBar, userSummaryLink, withdrawButton } from '../pages/walletDashboardPage'; +import { dashboardTab } from '../pages/walletPage'; When(/^I go to the dashboard screen$/, async function () { - await this.click({ locator: '.stakeDashboard ', method: 'css' }); + await this.click(dashboardTab); }); When(/^I click on the withdraw button$/, async function () { - await this.click({ locator: '.withdrawButton', method: 'css' }); + await this.click(withdrawButton); }); Then(/^I should rewards in the history$/, async function () { - await this.waitForElement({ locator: '.recharts-bar', method: 'css' }); + await this.waitForElement(rechartBar); }); When(/^I click on the unmangle warning$/, async function () { - await this.click({ locator: '.UserSummary_mangledWarningIcon', method: 'css' }); - await this.click({ locator: '.UserSummary_link', method: 'css' }); + await this.click(mangledWarningIcon); + await this.click(userSummaryLink); }); diff --git a/packages/yoroi-extension/features/step_definitions/general-settings-steps.js b/packages/yoroi-extension/features/step_definitions/general-settings-steps.js index a3ca6d8be3..f9507872fa 100644 --- a/packages/yoroi-extension/features/step_definitions/general-settings-steps.js +++ b/packages/yoroi-extension/features/step_definitions/general-settings-steps.js @@ -5,6 +5,7 @@ import { camelCase } from 'lodash'; import { waitUntilUrlEquals, navigateTo } from '../support/helpers/route-helpers'; import i18n from '../support/helpers/i18n-helpers'; import { By, WebElement } from 'selenium-webdriver'; +import { complexitySelected, secondThemeSelected, settingsLayoutComponent, complexityLevelForm, languageSelector } from '../pages/settingsPage'; export async function selectSubmenuSettings(customWorld: Object, buttonName: string) { const formattedButtonName = camelCase(buttonName); @@ -20,14 +21,14 @@ export async function goToSettings(customWorld: Object) { await navigateTo.call(customWorld, '/settings/general'); await waitUntilUrlEquals.call(customWorld, '/settings/general'); - await customWorld.waitForElement({ locator: '.SettingsLayout_component', method: 'css' }); + await customWorld.waitForElement(settingsLayoutComponent); } export async function getComplexityLevelButton( customWorld: Object, isLow: boolean = true ): Promise { - await customWorld.waitForElement({ locator: '.ComplexityLevelForm_cardsWrapper', method: 'css' }); + await customWorld.waitForElement(complexityLevelForm); const levels = await customWorld.driver.findElements(By.css('.ComplexityLevelForm_card')); let card; if (isLow) { @@ -50,11 +51,11 @@ When(/^I click on secondary menu "([^"]*)" item$/, async function (buttonName) { }); When(/^I select second theme$/, async function () { - await this.click({ locator: '.ThemeSettingsBlock_themesWrapper > button:nth-child(2)', method: 'css' }); + await this.click(secondThemeSelected); }); When(/^I open General Settings language selection dropdown$/, async function () { - await this.click({ locator: '//div[starts-with(@id, "languageId")]', method: 'xpath' }); + await this.click(languageSelector); }); Then(/^I should see secondary menu (.*) item disabled$/, async function (buttonName) { @@ -74,14 +75,11 @@ Then(/^The Japanese language should be selected$/, async function () { }); Then(/^I should see second theme as selected$/, async function () { - await this.waitForElement({ - locator: '.ThemeSettingsBlock_themesWrapper button:nth-child(2).ThemeSettingsBlock_active', - method: 'css' - }); + await this.waitForElement(secondThemeSelected); }); Then(/^The selected level is "([^"]*)"$/, async function (level) { - await this.waitUntilText({ locator: '.currentLevel', method: 'css' }, level.toUpperCase()); + await this.waitUntilText(complexitySelected, level.toUpperCase()); }); Then(/^I select the most complex level$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/hardware-steps.js b/packages/yoroi-extension/features/step_definitions/hardware-steps.js index 7eac9695ed..fd8adad636 100644 --- a/packages/yoroi-extension/features/step_definitions/hardware-steps.js +++ b/packages/yoroi-extension/features/step_definitions/hardware-steps.js @@ -5,28 +5,29 @@ import { testWallets } from '../mock-chain/TestWallets'; import { truncateAddress, } from '../../app/utils/formatters'; import { addressField, derivationField, verifyButton } from '../pages/verifyAddressPage'; import { expect } from 'chai'; +import { byronEraButton, pickUpCurrencyDialog, pickUpCurrencyDialogCardano, shelleyEraButton } from '../pages/newWalletPages'; When(/^I select a Byron-era Ledger device$/, async function () { await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); await this.click({ locator: '.WalletConnectHWOptionDialog_connectLedger', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }); + await this.click(byronEraButton); }); When(/^I select a Shelley-era Ledger device$/, async function () { await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); await this.click({ locator: '.WalletConnectHWOptionDialog_connectLedger', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }); + await this.click(shelleyEraButton); }); When(/^I restore the Ledger device$/, async function () { await this.waitForElement({ locator: '.CheckDialog_component', method: 'css' }); @@ -43,19 +44,19 @@ When(/^I restore the Ledger device$/, async function () { When(/^I select a Byron-era Trezor device$/, async function () { await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); await this.click({ locator: '.WalletConnectHWOptionDialog_connectTrezor', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }); + await this.click(byronEraButton); }); When(/^I select a Shelley-era Trezor device$/, async function () { await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); diff --git a/packages/yoroi-extension/features/step_definitions/select-language-steps.js b/packages/yoroi-extension/features/step_definitions/select-language-steps.js index 6acea5450c..7e9a73e191 100644 --- a/packages/yoroi-extension/features/step_definitions/select-language-steps.js +++ b/packages/yoroi-extension/features/step_definitions/select-language-steps.js @@ -3,6 +3,7 @@ import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; import languageSelection, { clickContinue } from '../support/helpers/language-selection-helpers'; +import { languageSelectionForm } from '../pages/basicSetupPage'; const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; @@ -11,7 +12,7 @@ Given(/^I have selected English language$/, async function () { }); When(/^I am on the language selection screen$/, async function () { - await this.waitForElement({ locator: LANGUAGE_SELECTION_FORM, method: 'css' }); + await this.waitForElement(languageSelectionForm); }); When(/^I open language selection dropdown$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js index 9567bda029..ff54f9924c 100644 --- a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js @@ -6,6 +6,7 @@ import { By, Key } from 'selenium-webdriver'; import { truncateLongName, } from '../../app/utils/formatters'; import { expect } from 'chai'; import { checkErrorByTranslationId } from './common-steps'; +import { walletNameText } from '../pages/walletPage'; const walletNameInputSelector = '.SettingsLayout_settingsPane .walletName input'; @@ -79,7 +80,7 @@ Then(/^I should not see the change password dialog anymore$/, async function () }); Then(/^I should see new wallet name "([^"]*)"$/, async function (walletName) { - await this.waitUntilText({ locator: '.NavPlate_name', method: 'css' }, truncateLongName(walletName)); + await this.waitUntilText(walletNameText, truncateLongName(walletName)); }); Then(/^I should see the following error messages:$/, async function (data) { diff --git a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js index 3a598eebc8..5c9234b8cc 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js @@ -10,6 +10,7 @@ import { getCurrencyButton, pickUpCurrencyDialog } from '../pages/newWalletPages'; +import { continueButton } from '../pages/basicSetupPage'; When(/^I click the create button$/, async function () { await this.click(createWalletButton); @@ -64,7 +65,7 @@ When(/^I accept the creation terms$/, async function () { ); const privacyChkbox = privacyDlg.findElement(By.xpath('//input[@type="checkbox"]')); privacyChkbox.click(); - await this.click({ locator: '//button[text()="Continue"]', method: 'xpath' }); + await this.click(continueButton); }); When(/^I copy and enter the displayed mnemonic phrase$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js index 511ee8506b..687be13eab 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js @@ -16,46 +16,47 @@ import { walletPasswordInput, } from '../pages/restoreWalletPage'; import { masterKeyInput } from '../pages/walletClaimTransferPage'; +import { pickUpCurrencyDialog, pickUpCurrencyDialogCardano, restoreNormalWallet, shelleyEraButton, walletRestoreDialog, walletRestoreOptionDialog } from '../pages/newWalletPages'; When(/^I click the restore button for ([^"]*)$/, async function (currency) { await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); await this.click({ locator: `.PickCurrencyOptionDialog_${currency}`, method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); + await this.waitForElement(walletRestoreOptionDialog); }); Then(/^I select Byron-era 15-word wallet$/, async function () { - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.click(byronEraButton); + await this.waitForElement(walletRestoreDialog); }); Then(/^I select Shelley-era 15-word wallet$/, async function () { - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.click(shelleyEraButton); + await this.waitForElement(walletRestoreDialog); }); Then(/^I select Shelley-era 24-word wallet$/, async function () { await this.click({ locator: '.WalletRestoreOptionDialog_normal24WordWallet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.waitForElement(walletRestoreDialog); }); Then(/^I select bip44 15-word wallet$/, async function () { - await this.click({ locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.click(restoreNormalWallet); + await this.waitForElement(walletRestoreDialog); }); When(/^I click the restore paper wallet button$/, async function () { await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); - await this.waitForElement({ locator: '.PickCurrencyOptionDialog', method: 'css' }); - await this.click({ locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }); + await this.waitForElement(pickUpCurrencyDialog); + await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletRestoreOptionDialog', method: 'css' }); + await this.waitForElement(walletRestoreOptionDialog); await this.click({ locator: '.WalletRestoreOptionDialog_restorePaperWallet', method: 'css' }); - await this.waitForElement({ locator: '.WalletRestoreDialog', method: 'css' }); + await this.waitForElement(walletRestoreDialog); }); When(/^I enter the recovery phrase:$/, async function (table) { diff --git a/packages/yoroi-extension/features/step_definitions/wallet-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-steps.js index c9bc69616c..a9a1b9e5b7 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-steps.js @@ -2,6 +2,7 @@ import { When, Then } from 'cucumber'; import { truncateLongName } from '../../app/utils/formatters'; +import { walletNameText } from '../pages/walletPage'; When(/^I enter the name "([^"]*)"$/, async function (walletName) { await this.input({ locator: "input[name='walletName']", method: 'css' }, walletName); @@ -13,11 +14,11 @@ When(/^I clear the name "([^"]*)"$/, async function (walletName) { When(/^I navigate to wallet sidebar category$/, async function () { await this.click({ locator: `//div[@class='Sidebar_categories']//button[1]`, method: 'xpath' }); - await this.waitForElement({ locator: '.NavPlate_name', method: 'css' }); + await this.waitForElement(walletNameText); }); Then(/^I should see the opened wallet with name "([^"]*)"$/, async function (walletName) { - await this.waitUntilText({ locator: '.NavPlate_name', method: 'css' }, truncateLongName(walletName)); + await this.waitUntilText(walletNameText, truncateLongName(walletName)); }); Then(/^I unselect the wallet$/, async function () { diff --git a/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js b/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js index 7d031f377d..d22abd7cf9 100644 --- a/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js +++ b/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js @@ -2,6 +2,7 @@ import i18n from './i18n-helpers'; import { By } from 'selenium-webdriver'; +import { languageSelectionForm } from '../../pages/basicSetupPage'; const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; @@ -11,9 +12,9 @@ const languageSelection = { { isHidden }: {| isHidden: boolean, |} = {} ): Promise => { if (isHidden) { - return client.waitForElementNotPresent({ locator: LANGUAGE_SELECTION_FORM, method: 'css' }); + return client.waitForElementNotPresent(languageSelectionForm); } - return client.waitForElement({ locator: LANGUAGE_SELECTION_FORM, method: 'css' }); + return client.waitForElement(languageSelectionForm); }, ensureLanguageIsSelected: async ( client: any, From 18940ae4e6e052dd12ae0545934d5a95e1058464 Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Mon, 29 Aug 2022 18:10:53 -0300 Subject: [PATCH 065/199] Moved more locators to corresponding pages --- .../features/pages/basicSetupPage.js | 22 +++- .../features/pages/commonDialogPage.js | 7 + .../features/pages/mainWindowPage.js | 4 +- .../features/pages/newWalletPages.js | 4 +- .../features/pages/settingsPage.js | 96 +++++++++++++- .../features/pages/sidebarPage.js | 6 +- .../features/pages/walletPage.js | 9 +- .../features/pages/walletReceivePage.js | 3 + .../features/pages/walletSendPage.js | 6 + .../features/pages/walletTransactionsPage.js | 4 +- .../step_definitions/hardware-steps.js | 90 +++++++------ .../installation-procedure-steps.js | 8 +- .../step_definitions/main-ui-steps.js | 64 +++++---- .../features/step_definitions/memo-steps.js | 28 ++-- .../step_definitions/select-language-steps.js | 9 +- .../step_definitions/settings-ui-steps.js | 121 ++++++++++-------- .../step_definitions/transactions-steps.js | 11 +- .../features/step_definitions/trezor-steps.js | 3 +- .../features/step_definitions/uri-steps.js | 3 +- .../step_definitions/wallet-creation-steps.js | 3 +- .../step_definitions/wallet-paper-steps.js | 3 +- .../wallet-restoration-steps.js | 6 +- .../features/step_definitions/wallet-steps.js | 5 +- .../step_definitions/yoroi-transfer-steps.js | 3 +- 24 files changed, 348 insertions(+), 170 deletions(-) create mode 100644 packages/yoroi-extension/features/pages/commonDialogPage.js create mode 100644 packages/yoroi-extension/features/pages/walletSendPage.js diff --git a/packages/yoroi-extension/features/pages/basicSetupPage.js b/packages/yoroi-extension/features/pages/basicSetupPage.js index 7cf2f79b09..381f53a890 100644 --- a/packages/yoroi-extension/features/pages/basicSetupPage.js +++ b/packages/yoroi-extension/features/pages/basicSetupPage.js @@ -3,11 +3,23 @@ import type { LocatorObject } from '../support/webdriver'; // language select page -export const languageSelectionForm: LocatorObject = { locator: '.LanguageSelectionForm_component', method: 'css' }; +export const languageSelectionForm: LocatorObject = { + locator: '.LanguageSelectionForm_component', + method: 'css', +}; +export const japaneseLaguageSelection: LocatorObject = { + locator: '//span[contains(text(), "日本語")]', + method: 'xpath', +}; - -export const continueButton: LocatorObject = { locator: '//button[text()="Continue"]', method: 'xpath' }; +export const continueButton: LocatorObject = { + locator: '//button[text()="Continue"]', + method: 'xpath', +}; // ToS page -export const termsOfUseComponent: LocatorObject = { locator: '.TermsOfUseForm_component', method: 'css' }; +export const termsOfUseComponent: LocatorObject = { + locator: '.TermsOfUseForm_component', + method: 'css', +}; // uri prompt page -export const walletAddComponent: LocatorObject ={ locator: '.WalletAdd_component', method: 'css' }; \ No newline at end of file +export const walletAddComponent: LocatorObject = { locator: '.WalletAdd_component', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/commonDialogPage.js b/packages/yoroi-extension/features/pages/commonDialogPage.js new file mode 100644 index 0000000000..242f03173f --- /dev/null +++ b/packages/yoroi-extension/features/pages/commonDialogPage.js @@ -0,0 +1,7 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const primaryButton: LocatorObject = { locator: '.primary', method: 'css' }; +export const errorBlockComponent: LocatorObject = { locator: '.ErrorBlock_component', method: 'css' }; +export const dialogTitle = { locator: '.dialog__title', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/mainWindowPage.js b/packages/yoroi-extension/features/pages/mainWindowPage.js index d80f6e1609..c2e14ce0e2 100644 --- a/packages/yoroi-extension/features/pages/mainWindowPage.js +++ b/packages/yoroi-extension/features/pages/mainWindowPage.js @@ -2,4 +2,6 @@ import type { LocatorObject } from '../support/webdriver'; -export const yoroiClassic: LocatorObject = { locator: '.YoroiClassic', method: 'css' } \ No newline at end of file +export const yoroiClassic: LocatorObject = { locator: '.YoroiClassic', method: 'css' }; +export const serverErrorBanner = { locator: '.ServerErrorBanner_serverError', method: 'css' }; +export const maintenanceBody = { locator: '.Maintenance_body', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/newWalletPages.js b/packages/yoroi-extension/features/pages/newWalletPages.js index 473a61f47f..2be897e07a 100644 --- a/packages/yoroi-extension/features/pages/newWalletPages.js +++ b/packages/yoroi-extension/features/pages/newWalletPages.js @@ -31,4 +31,6 @@ export const trezorConfirmButton: LocatorObject = { locator: '.MuiButton-primary // Common elements export const walletNameInput: LocatorObject = { locator: '//input[@name="walletName"]', method: 'xpath' }; export const saveDialog: LocatorObject = { locator: '.SaveDialog', method: 'css' }; -export const saveButton: LocatorObject = { locator: '//button[@id="primaryButton"]', method: 'xpath' }; \ No newline at end of file +export const saveButton: LocatorObject = { locator: '//button[@id="primaryButton"]', method: 'xpath' }; +export const checkDialog: LocatorObject = { locator: '.CheckDialog_component', method: 'css' }; +export const sendConfirmationDialog: LocatorObject = { locator: '.HWSendConfirmationDialog_dialog', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/settingsPage.js b/packages/yoroi-extension/features/pages/settingsPage.js index 2ec9e1b36f..7c32bd10e9 100644 --- a/packages/yoroi-extension/features/pages/settingsPage.js +++ b/packages/yoroi-extension/features/pages/settingsPage.js @@ -2,14 +2,96 @@ import type { LocatorObject } from '../support/webdriver'; -export const settingsLayoutComponent: LocatorObject = { locator: '.SettingsLayout_component', method: 'css' }; +export const fullScreenMessage = { locator: '.FullscreenMessage_title', method: 'css' }; + +// Wallet tab + +export const walletNameInputSelector = { + locator: '.SettingsLayout_settingsPane .walletName input', + method: 'css', +}; +export const walletNameInput = { + locator: '.SettingsLayout_settingsPane .InlineEditingInput_component', + method: 'css', +}; +export const walletSettingsPane = { + locator: '.SettingsLayout_settingsPane', + method: 'css', +}; +export const removeWalletButton = { locator: '.removeWallet', method: 'css' }; +export const resyncWalletButton = { locator: '.resyncButton', method: 'css' }; +export const exportButton = { locator: '.exportWallet', method: 'css' }; +export const exportPublicKeyDialog = { locator: '.ExportPublicKeyDialog_component', method: 'css' }; + +// Level of complexity tab + +export const complexityLevelForm: LocatorObject = { + locator: '.ComplexityLevelForm_cardsWrapper', + method: 'css', +}; +export const complexitySelected: LocatorObject = { locator: '.currentLevel', method: 'css' }; + +// General tab + +export const languageSelector: LocatorObject = { + locator: '//div[starts-with(@id, "languageId")]', + method: 'xpath', +}; +export const settingsLayoutComponent: LocatorObject = { + locator: '.SettingsLayout_component', + method: 'css', +}; export const secondThemeSelected: LocatorObject = { - locator: '.ThemeSettingsBlock_themesWrapper button:nth-child(2).ThemeSettingsBlock_active', - method: 'css' - }; + locator: '.ThemeSettingsBlock_themesWrapper button:nth-child(2).ThemeSettingsBlock_active', + method: 'css', +}; + +// Change password dialog + +export const currentPasswordInput: LocatorObject = { + locator: '.changePasswordDialog .currentPassword input', + method: 'css', +}; +export const newPasswordInput: LocatorObject = { + locator: '.changePasswordDialog .newPassword input', + method: 'css', +}; +export const repeatPasswordInput: LocatorObject = { + locator: '.changePasswordDialog .repeatedPassword input', + method: 'css', +}; +export const confirmButton = { locator: '.confirmButton', method: 'css' }; +export const changePasswordDialog = { locator: '.changePasswordDialog', method: 'css' }; +export const walletPasswordHelperText = { + locator: '//p[starts-with(@id, "walletPassword--") and contains(@id, "-helper-text")]', + method: 'xpath', +}; +export const helperText = { locator: '.MuiFormHelperText-root', method: 'css' }; +export const changePasswordDialogError = { + locator: '.ChangeWalletPasswordDialog_error', + method: 'css', +}; + +// Support/Logs Tab - export const complexityLevelForm: LocatorObject = { locator: '.ComplexityLevelForm_cardsWrapper', method: 'css' }; - export const complexitySelected: LocatorObject = { locator: '.currentLevel', method: 'css' }; - export const languageSelector: LocatorObject = { locator: '//div[starts-with(@id, "languageId")]', method: 'xpath' }; +export const faqTitle = { + locator: "//h1[contains(text(), 'Frequently asked questions')]", + method: 'xpath', +}; +export const reportingAProblemTitle = { + locator: "//h1[contains(text(), 'Reporting a problem')]", + method: 'xpath', +}; +export const logsTitle = { locator: "//h1[contains(text(), 'Logs')]", method: 'xpath' }; +// Blockchain Tab +export const explorerSettingsDropdown = { locator: '.ExplorerSettings_component', method: 'css' }; +export const cardanoPaymentsURLTitle = { + locator: "//h2[contains(text(), 'Cardano Payment URLs')]", + method: 'xpath', +}; +export const currencyConversionText = { + locator: "//h2[contains(text(), 'Currency Conversion')]", + method: 'xpath', +}; diff --git a/packages/yoroi-extension/features/pages/sidebarPage.js b/packages/yoroi-extension/features/pages/sidebarPage.js index b3b7151aa2..97e9554fa8 100644 --- a/packages/yoroi-extension/features/pages/sidebarPage.js +++ b/packages/yoroi-extension/features/pages/sidebarPage.js @@ -6,4 +6,8 @@ export const stakingButton = { locator: 'sidebar.staking', method: 'id' }; export const assetsButton = { locator: 'sidebar.assets', method: 'id' }; export const votingButton = { locator: 'sidebar.voting', method: 'id' }; export const settingsButton = { locator: 'sidebar.settings', method: 'id' }; -export const faqButton = { locator: '.SidebarRevamp_faq', method: 'css' }; \ No newline at end of file +export const faqButton = { locator: '.SidebarRevamp_faq', method: 'css' }; + +// Classic version elements + +export const walletButtonClassic = { locator: `//div[@class='Sidebar_categories']//button[1]`, method: 'xpath' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/walletPage.js b/packages/yoroi-extension/features/pages/walletPage.js index e1a0f7bb8c..5d74816da8 100644 --- a/packages/yoroi-extension/features/pages/walletPage.js +++ b/packages/yoroi-extension/features/pages/walletPage.js @@ -8,4 +8,11 @@ export const claimTransferTab = { locator: '.claimTransfer', method: 'css' }; export const walletNameText = { locator: '.NavPlate_name', method: 'css' }; export const activeNavTab = { locator: '.WalletNavButton_active', method: 'css' }; -export const dashboardTab = { locator: '.stakeDashboard ', method: 'css' }; \ No newline at end of file +export const dashboardTab = { locator: '.stakeDashboard ', method: 'css' }; +export const transactionsTab = { locator: `//span[contains(text(), "Transactions")]`, method: 'xpath' }; + +export const navDetailsAmount = { locator: '.NavWalletDetails_amount', method: 'css' }; +export const navDetailsHideButton = { locator: '.NavWalletDetails_toggleButton', method: 'css' }; +export const navDetailsWalletDropdown = { locator: '.NavDropdown_toggle', method: 'css' }; +export const navDetailsBuyButton = { locator: '.NavDropdownContent_buyButton', method: 'css' }; +export const buyDialogAddress = { locator: '.BuySellDialog_address', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/walletReceivePage.js b/packages/yoroi-extension/features/pages/walletReceivePage.js index 387bb35067..e87c696336 100644 --- a/packages/yoroi-extension/features/pages/walletReceivePage.js +++ b/packages/yoroi-extension/features/pages/walletReceivePage.js @@ -21,3 +21,6 @@ export const generateAddressButton = { locator: '.generateAddressButton', method export const addressBookTab = { locator: `.addressBook`, method: 'css' }; export const rewardAddressTab = { locator: `.reward`, method: 'css' }; export const yourWalletAddrHeader = { locator: '.StandardHeader_copyableHash', method: 'css' }; +export const verifyAddressButton = { locator: '.WalletReceive_verifyIcon', method: 'css' }; + +export const verifyAddressHWButton = { locator: '.VerifyAddressDialog_component .primary', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js new file mode 100644 index 0000000000..8cbbeb2f7a --- /dev/null +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -0,0 +1,6 @@ +// @flow + +export const receiverInput = { locator: "input[name='receiver']", method: 'css' }; +export const amountInput = { locator: "input[name='amount']", method: 'css' }; +export const addMemoButton = { locator: '.addMemoButton', method: 'css' }; +export const memoContentInput = { locator: "input[name='memoContent']", method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletTransactionsPage.js b/packages/yoroi-extension/features/pages/walletTransactionsPage.js index 83ac150032..73041831de 100644 --- a/packages/yoroi-extension/features/pages/walletTransactionsPage.js +++ b/packages/yoroi-extension/features/pages/walletTransactionsPage.js @@ -1,3 +1,5 @@ // @flow -export const walletSummaryBox = { locator: 'walletSummary_box', method: 'id' } \ No newline at end of file +export const walletSummaryBox = { locator: 'walletSummary_box', method: 'id' }; +export const walletSummaryComponent = { locator: "//div[@class='WalletSummary_component']", method: 'xpath' }; +export const copyToClipboardButton = { locator: '.CopyableAddress_copyIconBig', method: 'css' }; diff --git a/packages/yoroi-extension/features/step_definitions/hardware-steps.js b/packages/yoroi-extension/features/step_definitions/hardware-steps.js index fd8adad636..80a8eec158 100644 --- a/packages/yoroi-extension/features/step_definitions/hardware-steps.js +++ b/packages/yoroi-extension/features/step_definitions/hardware-steps.js @@ -1,97 +1,113 @@ // @flow -import { When, Then, } from 'cucumber'; +import { When, Then } from 'cucumber'; import { testWallets } from '../mock-chain/TestWallets'; -import { truncateAddress, } from '../../app/utils/formatters'; +import { truncateAddress } from '../../app/utils/formatters'; import { addressField, derivationField, verifyButton } from '../pages/verifyAddressPage'; import { expect } from 'chai'; -import { byronEraButton, pickUpCurrencyDialog, pickUpCurrencyDialogCardano, shelleyEraButton } from '../pages/newWalletPages'; +import { + byronEraButton, + checkDialog, + connectHwButton, + hwOptionsDialog, + ledgerWalletButton, + pickUpCurrencyDialog, + pickUpCurrencyDialogCardano, + saveDialog, + sendConfirmationDialog, + shelleyEraButton, + trezorWalletButton, +} from '../pages/newWalletPages'; +import { errorBlockComponent, primaryButton } from '../pages/commonDialogPage'; +import { walletNameInput } from '../pages/restoreWalletPage'; +import { verifyAddressButton, verifyAddressHWButton } from '../pages/walletReceivePage'; When(/^I select a Byron-era Ledger device$/, async function () { - await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); + await this.click(connectHwButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); + await this.waitForElement(hwOptionsDialog); - await this.click({ locator: '.WalletConnectHWOptionDialog_connectLedger', method: 'css' }); + await this.click(ledgerWalletButton); await this.click(byronEraButton); }); When(/^I select a Shelley-era Ledger device$/, async function () { - await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); + await this.click(connectHwButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); + await this.waitForElement(hwOptionsDialog); - await this.click({ locator: '.WalletConnectHWOptionDialog_connectLedger', method: 'css' }); + await this.click(ledgerWalletButton); await this.click(shelleyEraButton); }); When(/^I restore the Ledger device$/, async function () { - await this.waitForElement({ locator: '.CheckDialog_component', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); + await this.waitForElement(checkDialog); + await this.click(primaryButton); + await this.click(primaryButton); // between these is where the tab & iframe gets opened - await this.waitForElement({ locator: '.SaveDialog', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); + await this.waitForElement(saveDialog); + await this.click(primaryButton); }); - When(/^I select a Byron-era Trezor device$/, async function () { - await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); + await this.click(connectHwButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); + await this.waitForElement(hwOptionsDialog); - await this.click({ locator: '.WalletConnectHWOptionDialog_connectTrezor', method: 'css' }); + await this.click(trezorWalletButton); await this.click(byronEraButton); }); When(/^I select a Shelley-era Trezor device$/, async function () { - await this.click({ locator: '.WalletAdd_btnConnectHW', method: 'css' }); + await this.click(connectHwButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement({ locator: '.WalletConnectHWOptionDialog', method: 'css' }); + await this.waitForElement(hwOptionsDialog); - await this.click({ locator: '.WalletConnectHWOptionDialog_connectTrezor', method: 'css' }); - await this.click({ locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }); + await this.click(trezorWalletButton); + await this.click(shelleyEraButton); }); When(/^I restore the Trezor device$/, async function () { - await this.waitForElement({ locator: '.CheckDialog_component', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); + await this.waitForElement(checkDialog); + await this.click(primaryButton); + await this.click(primaryButton); // between these is where the tab & iframe gets opened - await this.waitForElement({ locator: '.SaveDialog', method: 'css' }); - await this.input({ locator: "input[name='walletName']", method: 'css' }, testWallets['trezor-wallet'].name); - await this.click({ locator: '.primary', method: 'css' }); + await this.waitForElement(saveDialog); + await this.input(walletNameInput, testWallets['trezor-wallet'].name); + await this.click(primaryButton); }); - When(/^I see the hardware send money confirmation dialog$/, async function () { - await this.waitForElement({ locator: '.HWSendConfirmationDialog_dialog', method: 'css' }); + await this.waitForElement(sendConfirmationDialog); }); When(/^I click on the verify address button$/, async function () { // wait until all addresses are loaded await this.driver.sleep(1000); - const allVerifyAddressButtons = await this.findElements({ locator: '.WalletReceive_verifyIcon', method: 'css' }); + const allVerifyAddressButtons = await this.findElements(verifyAddressButton); await allVerifyAddressButtons[0].click(); }); When(/^I see the verification address "([^"]*)"$/, async function (expectAddress) { await this.waitForElement(addressField); const actualAddressStr = await this.getText(addressField); - expect(actualAddressStr).to.equal(truncateAddress(expectAddress), `The actual verification address is different from expected.`); + expect(actualAddressStr).to.equal( + truncateAddress(expectAddress), + `The actual verification address is different from expected.` + ); }); When(/^I see the derivation path "([^"]*)"$/, async function (path) { @@ -99,15 +115,15 @@ When(/^I see the derivation path "([^"]*)"$/, async function (path) { }); Then(/^I verify the address on my ledger device$/, async function () { - await this.click({ locator: '.VerifyAddressDialog_component .primary', method: 'css' }); - await this.waitDisable({ locator: '.VerifyAddressDialog_component .primary', method: 'css' }); // disable when communicating with device - await this.waitEnable({ locator: '.VerifyAddressDialog_component .primary', method: 'css' }); // enable after it's done + await this.click(verifyAddressHWButton); + await this.waitDisable(verifyAddressHWButton); // disable when communicating with device + await this.waitEnable(verifyAddressHWButton); // enable after it's done await this.driver.sleep(1000); - await this.waitForElementNotPresent({ locator: '.ErrorBlock_component', method: 'css' }); + await this.waitForElementNotPresent(errorBlockComponent); }); Then(/^I verify the address on my trezor device$/, async function () { await this.click(verifyButton); // we should have this disable while the action is processing, but we don't show a spinner on this - await this.waitForElementNotPresent({ locator: '.ErrorBlock_component', method: 'css' }); + await this.waitForElementNotPresent(errorBlockComponent); }); diff --git a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js index 8478bf7ae9..96e66f830d 100644 --- a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js +++ b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js @@ -3,11 +3,12 @@ import { Given, When, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import { expect } from 'chai'; +import { termsOfUseComponent } from '../pages/basicSetupPage'; const TERMS_OF_USE_FORM = '.TermsOfUseForm_component'; Given(/^I am on the "Terms of use" screen$/, async function () { - await this.waitForElement({ locator: TERMS_OF_USE_FORM, method: 'css' }); + await this.waitForElement(termsOfUseComponent); }); When(/^I click on "I agree with the terms of use" checkbox$/, async function () { @@ -23,10 +24,7 @@ When(/^I submit the "Terms of use" form$/, async function () { }); Then(/^I should not see the "Terms of use" screen anymore$/, async function () { - await this.waitForElementNotPresent({ - locator: TERMS_OF_USE_FORM, - method: 'css' - }); + await this.waitForElementNotPresent(termsOfUseComponent); }); Then(/^I should have "Terms of use" accepted$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/main-ui-steps.js b/packages/yoroi-extension/features/step_definitions/main-ui-steps.js index c10d86de3c..c6a479b9e8 100644 --- a/packages/yoroi-extension/features/step_definitions/main-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/main-ui-steps.js @@ -4,72 +4,92 @@ import { When, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import { hiddenAmount } from '../../app/utils/strings'; -import { truncateAddress, } from '../../app/utils/formatters'; +import { truncateAddress } from '../../app/utils/formatters'; +import { + buyDialogAddress, + navDetailsAmount, + navDetailsBuyButton, + navDetailsHideButton, + navDetailsWalletDropdown, + transactionsTab, +} from '../pages/walletPage'; +import { amountInput, receiverInput } from '../pages/walletSendPage'; +import { walletButtonClassic } from '../pages/sidebarPage'; +import { copyToClipboardButton, walletSummaryComponent } from '../pages/walletTransactionsPage'; +import { maintenanceBody, serverErrorBanner } from '../pages/mainWindowPage'; Then(/^I should see the balance number "([^"]*)"$/, async function (number) { - await this.waitUntilText({ locator: '.NavWalletDetails_amount', method: 'css' }, number); + await this.waitUntilText(navDetailsAmount, number); }); Then(/^I should see send transaction screen$/, async function () { - await this.waitForElement({ locator: "input[name='receiver']", method: 'css' }); - await this.waitForElement({ locator: "input[name='amount']", method: 'css' }); + await this.waitForElement(receiverInput); + await this.waitForElement(amountInput); }); Then(/^I go to the transaction history screen$/, async function () { - await this.click({ locator: `//span[contains(text(), "Transactions")]`, method: 'xpath' }); + await this.click(transactionsTab); }); When(/^I go to the main screen$/, async function () { - await this.click({ locator: `//div[@class='Sidebar_categories']//button[1]`, method: 'xpath' }); + await this.click(walletButtonClassic); }); Then(/^I should see the transactions screen$/, async function () { - await this.waitForElement({ locator: "//div[@class='WalletSummary_component']", method: 'xpath' }); + await this.waitForElement(walletSummaryComponent); }); Then(/^I click on "copy to clipboard" button$/, async function () { - await this.click({ locator: '.CopyableAddress_copyIconBig', method: 'css' }); + await this.click(copyToClipboardButton); }); Then(/^I should see "copied" tooltip message:$/, async function (data) { const notification = data.hashes()[0]; const notificationMessage = await this.intl(notification.message); - const messageParentElement = await this.driver.findElement(By.xpath('//div[contains(@role, "tooltip")]')); - const message = await messageParentElement.findElement(By.xpath(`//span[contains(text(), "${notificationMessage}")]`)); + const messageParentElement = await this.driver.findElement( + By.xpath('//div[contains(@role, "tooltip")]') + ); + const message = await messageParentElement.findElement( + By.xpath(`//span[contains(text(), "${notificationMessage}")]`) + ); expect(await message.isDisplayed()).to.be.true; }); Then(/^I see transactions buttons are disabled$/, async function () { - const disabledButtons = await this.driver.findElement(By.xpath("//button[contains(@class, 'confirmButton') and contains(@class, 'disabled')]")); + const disabledButtons = await this.driver.findElement( + By.xpath("//button[contains(@class, 'confirmButton') and contains(@class, 'disabled')]") + ); const pageUrl = await this.driver.getCurrentUrl(); disabledButtons.click(); expect(pageUrl).to.be.equal(await this.driver.getCurrentUrl()); }); Then(/^I should see the networkError banner$/, async function () { - await this.waitForElement({ locator: '.ServerErrorBanner_serverError', method: 'css' }); + await this.waitForElement(serverErrorBanner); }); Then(/^I should see the serverError banner$/, async function () { - await this.waitForElement({ locator: '.ServerErrorBanner_serverError', method: 'css' }); + await this.waitForElement(serverErrorBanner); }); Then(/^I should see the app maintenance page$/, async function () { - await this.waitForElement({ locator: '.Maintenance_body', method: 'css' }); + await this.waitForElement(maintenanceBody); }); Then(/^I click on hide balance button$/, async function () { - await this.click({ locator: '.NavWalletDetails_toggleButton', method: 'css' }); + await this.click(navDetailsHideButton); }); Then(/^I should see my balance hidden$/, async function () { - await this.waitForElement({ locator: '.NavWalletDetails_amount', method: 'css' }); - await this.waitUntilContainsText({ locator: '.NavWalletDetails_amount', method: 'css' }, hiddenAmount); + await this.waitForElement(navDetailsAmount); + await this.waitUntilContainsText(navDetailsAmount, hiddenAmount); }); Then(/^I switch to "([^"]*)" from the dropdown$/, async function (walletName) { - await this.click({ locator: '.NavDropdown_toggle', method: 'css' }); - const wallets = await this.driver.findElements(By.xpath("//button[contains(@class, 'NavDropdownRow_head')]")); + await this.click(navDetailsWalletDropdown); + const wallets = await this.driver.findElements( + By.xpath("//button[contains(@class, 'NavDropdownRow_head')]") + ); for (const wallet of wallets) { const nameElem = await wallet.findElement(By.css('.NavPlate_name')); const foundName = await nameElem.getText(); @@ -82,10 +102,10 @@ Then(/^I switch to "([^"]*)" from the dropdown$/, async function (walletName) { }); Then(/^I select buy-sell from the dropdown$/, async function () { - await this.click({ locator: '.NavDropdown_toggle', method: 'css' }); - await this.click({ locator: '.NavDropdownContent_buyButton', method: 'css' }); + await this.click(navDetailsWalletDropdown); + await this.click(navDetailsBuyButton); }); Then(/^I should see the pre-filled address "([^"]*)"$/, async function (address) { - await this.waitUntilContainsText({ locator: '.BuySellDialog_address', method: 'css' }, truncateAddress(address)); + await this.waitUntilContainsText(buyDialogAddress, truncateAddress(address)); }); diff --git a/packages/yoroi-extension/features/step_definitions/memo-steps.js b/packages/yoroi-extension/features/step_definitions/memo-steps.js index aa81507ba8..9327fdc819 100644 --- a/packages/yoroi-extension/features/step_definitions/memo-steps.js +++ b/packages/yoroi-extension/features/step_definitions/memo-steps.js @@ -4,12 +4,14 @@ import { Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import chai from 'chai'; import { MAX_MEMO_SIZE } from '../../app/config/externalStorageConfig'; +import { primaryButton } from '../pages/commonDialogPage'; +import { addMemoButton, memoContentInput } from '../pages/walletSendPage'; Then(/^I add a memo that says "([^"]*)"$/, async function (memo) { - await this.click({ locator: '.addMemoButton', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); - await this.input({ locator: "input[name='memoContent']", method: 'css' }, memo); - await this.click({ locator: '.primary', method: 'css' }); + await this.click(addMemoButton); + await this.click(primaryButton); + await this.input(memoContentInput, memo); + await this.click(primaryButton); }); Then(/^The memo content says "([^"]*)"$/, async function (memo) { @@ -21,15 +23,15 @@ Then(/^The memo content says "([^"]*)"$/, async function (memo) { Then(/^I edit the memo to say "([^"]*)"$/, async function (memo) { await this.click({ locator: '.editMemoButton', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); - await this.clearInputUpdatingForm({ locator: "input[name='memoContent']", method: 'css' }, MAX_MEMO_SIZE); - await this.input({ locator: "input[name='memoContent']", method: 'css' }, memo); - await this.click({ locator: '.primary', method: 'css' }); + await this.click(primaryButton); + await this.clearInputUpdatingForm(memoContentInput, MAX_MEMO_SIZE); + await this.input(memoContentInput, memo); + await this.click(primaryButton); }); Then(/^I delete the memo$/, async function () { await this.click({ locator: '.editMemoButton', method: 'css' }); - await this.click({ locator: '.primary', method: 'css' }); + await this.click(primaryButton); let memoComponent = await this.driver.findElement(By.css('.MemoDialogCommon_component')); const deleteButton = await memoComponent.findElement(By.xpath('//button[@aria-label="delete memo"]')); await deleteButton.click(); @@ -39,14 +41,14 @@ Then(/^I delete the memo$/, async function () { }); Then(/^There is no memo for the transaction$/, async function () { - await this.waitForElement({ locator: '.addMemoButton', method: 'css' }); + await this.waitForElement(addMemoButton); }); Then(/^I add a transaction memo that says "([^"]*)"$/, async function (memo) { await this.driver.sleep(500); - await this.click({ locator: '.addMemoButton', method: 'css' }); + await this.click(addMemoButton); await this.driver.sleep(500); - await this.click({ locator: '.MemoDialogCommon_component .primary', method: 'css' }); + await this.click(primaryButton); await this.driver.sleep(500); - await this.input({ locator: "input[name='memo']", method: 'css' }, memo); + await this.input(memoContentInput, memo); }); diff --git a/packages/yoroi-extension/features/step_definitions/select-language-steps.js b/packages/yoroi-extension/features/step_definitions/select-language-steps.js index 7e9a73e191..36f257800c 100644 --- a/packages/yoroi-extension/features/step_definitions/select-language-steps.js +++ b/packages/yoroi-extension/features/step_definitions/select-language-steps.js @@ -3,7 +3,7 @@ import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; import languageSelection, { clickContinue } from '../support/helpers/language-selection-helpers'; -import { languageSelectionForm } from '../pages/basicSetupPage'; +import { japaneseLaguageSelection, languageSelectionForm } from '../pages/basicSetupPage'; const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; @@ -20,7 +20,7 @@ When(/^I open language selection dropdown$/, async function () { }); When(/^I select Japanese language$/, async function () { - return this.click({ locator: '//span[contains(text(), "日本語")]', method: 'xpath' }); + return this.click(japaneseLaguageSelection); }); When(/^I submit the language selection form$/, async function () { @@ -28,10 +28,7 @@ When(/^I submit the language selection form$/, async function () { }); Then(/^I should not see the language selection screen anymore$/, async function () { - await this.waitForElementNotPresent({ - locator: LANGUAGE_SELECTION_FORM, - method: 'css' - }); + await this.waitForElementNotPresent(languageSelectionForm); }); Then(/^I should have Japanese language set$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js index ff54f9924c..b18e47b66d 100644 --- a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js @@ -3,12 +3,35 @@ import { When, Given, Then } from 'cucumber'; import i18n from '../support/helpers/i18n-helpers'; import { By, Key } from 'selenium-webdriver'; -import { truncateLongName, } from '../../app/utils/formatters'; +import { truncateLongName } from '../../app/utils/formatters'; import { expect } from 'chai'; import { checkErrorByTranslationId } from './common-steps'; import { walletNameText } from '../pages/walletPage'; - -const walletNameInputSelector = '.SettingsLayout_settingsPane .walletName input'; +import { + changePasswordDialog, + changePasswordDialogError, + confirmButton, + currentPasswordInput, + explorerSettingsDropdown, + helperText, + newPasswordInput, + repeatPasswordInput, + walletNameInput, + walletNameInputSelector, + walletPasswordHelperText, + walletSettingsPane, + faqTitle, + logsTitle, + reportingAProblemTitle, + cardanoPaymentsURLTitle, + currencyConversionText, + removeWalletButton, + resyncWalletButton, + exportButton, + exportPublicKeyDialog, + fullScreenMessage, +} from '../pages/settingsPage'; +import { dialogTitle } from '../pages/commonDialogPage'; Given(/^I should see the "([^"]*)" wallet password dialog$/, async function (dialogType) { const selector = '.' + dialogType + 'PasswordDialog'; @@ -16,7 +39,7 @@ Given(/^I should see the "([^"]*)" wallet password dialog$/, async function (dia }); When(/^I click on "name" input field$/, async function () { - await this.click({ locator: '.SettingsLayout_settingsPane .InlineEditingInput_component', method: 'css' }); + await this.click(walletNameInput); }); When(/^I enter new wallet name:$/, async function (table) { @@ -26,17 +49,17 @@ When(/^I enter new wallet name:$/, async function (table) { * This makes our InlineEditingInput become disabled causing the clear and sendKeys to fail * https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/214 * We instead repeatedly delete characters until we've deleted the whole name - */ + */ // can't programmatically get the wallet name due to the issue above // so assume max length const maxNameLength = 40; for (let i = 0; i < maxNameLength; i++) { // Chrome and Firefox select the text field starting at the left / right respectively - await this.input({ locator: walletNameInputSelector, method: 'css' }, Key.BACK_SPACE); // Firefox - await this.input({ locator: walletNameInputSelector, method: 'css' }, Key.DELETE); // Chrome + await this.input(walletNameInputSelector, Key.BACK_SPACE); // Firefox + await this.input(walletNameInputSelector, Key.DELETE); // Chrome } - await this.input({ locator: walletNameInputSelector, method: 'css' }, fields.name); + await this.input(walletNameInputSelector, fields.name); }); Then(/^I should see the "Terms of use" screen$/, async function () { @@ -44,7 +67,7 @@ Then(/^I should see the "Terms of use" screen$/, async function () { }); When(/^I click outside "name" input field$/, async function () { - await this.click({ locator: '.SettingsLayout_settingsPane', method: 'css' }); + await this.click(walletSettingsPane); }); When(/^I click on the "([^"]*)" password label$/, async function (label) { @@ -54,29 +77,29 @@ When(/^I click on the "([^"]*)" password label$/, async function (label) { When(/^I change wallet password:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: '.changePasswordDialog .currentPassword input', method: 'css' }, fields.currentPassword); - await this.input({ locator: '.changePasswordDialog .newPassword input', method: 'css' }, fields.password); - await this.input({ locator: '.changePasswordDialog .repeatedPassword input', method: 'css' }, fields.repeatedPassword); + await this.input(currentPasswordInput, fields.currentPassword); + await this.input(newPasswordInput, fields.password); + await this.input(repeatPasswordInput, fields.repeatedPassword); }); When(/^I clear the current wallet password ([^"]*)$/, async function (password) { - await this.clearInputUpdatingForm({ locator: '.changePasswordDialog .currentPassword input', method: 'css' }, password.length); + await this.clearInputUpdatingForm(currentPasswordInput, password.length); }); When(/^I clear the current wallet repeat password ([^"]*)$/, async function (repeatPassword) { - await this.clearInputUpdatingForm({ locator: '.changePasswordDialog .repeatedPassword input', method: 'css' }, repeatPassword.length); + await this.clearInputUpdatingForm(repeatPasswordInput, repeatPassword.length); }); When(/^I submit the wallet password dialog$/, async function () { - await this.click({ locator: '.confirmButton', method: 'css' }); + await this.click(confirmButton); }); When(/^I click the next button$/, async function () { - await this.click({ locator: '.confirmButton', method: 'css' }); + await this.click(confirmButton); }); Then(/^I should not see the change password dialog anymore$/, async function () { - await this.waitForElementNotPresent({ locator: '.changePasswordDialog', method: 'css' }); + await this.waitForElementNotPresent(changePasswordDialog); }); Then(/^I should see new wallet name "([^"]*)"$/, async function (walletName) { @@ -85,88 +108,74 @@ Then(/^I should see new wallet name "([^"]*)"$/, async function (walletName) { Then(/^I should see the following error messages:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '//p[starts-with(@id, "walletPassword--") and contains(@id, "-helper-text")]', method: 'xpath' }, - error); + await checkErrorByTranslationId(this, walletPasswordHelperText, error); }); Then(/^I should see "Doesn't match" error message:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '.MuiFormHelperText-root', method: 'css' }, - error); + await checkErrorByTranslationId(this, helperText, error); }); Then(/^I should see the following submit error messages:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '.ChangeWalletPasswordDialog_error', method: 'css' }, - error); + await checkErrorByTranslationId(this, changePasswordDialogError, error); }); Then(/^I should stay in the change password dialog$/, async function () { - const changePasswordMessage = await i18n.formatMessage(this.driver, - { id: 'wallet.settings.changePassword.dialog.title.changePassword' }); - await this.waitUntilText({ locator: '.dialog__title', method: 'css' }, changePasswordMessage.toUpperCase(), 2000); + const changePasswordMessage = await i18n.formatMessage(this.driver, { + id: 'wallet.settings.changePassword.dialog.title.changePassword', + }); + await this.waitUntilText(dialogTitle, changePasswordMessage.toUpperCase(), 2000); }); Then(/^I should see support screen$/, async function () { - await this.waitForElement({ locator: "//h1[contains(text(), 'Frequently asked questions')]", method: 'xpath' }); - await this.waitForElement({ locator: "//h1[contains(text(), 'Reporting a problem')]", method: 'xpath' }); - await this.waitForElement({ locator: "//h1[contains(text(), 'Logs')]", method: 'xpath' }); + await this.waitForElement(faqTitle); + await this.waitForElement(reportingAProblemTitle); + await this.waitForElement(logsTitle); }); Then(/^I should see blockchain screen$/, async function () { - await this.waitForElement({ locator: '.ExplorerSettings_component', method: 'css' }); - await this.waitForElement({ locator: "//h2[contains(text(), 'Cardano Payment URLs')]", method: 'xpath' }); - await this.waitForElement({ locator: "//h2[contains(text(), 'Currency Conversion')]", method: 'xpath' }); + await this.waitForElement(explorerSettingsDropdown); + await this.waitForElement(cardanoPaymentsURLTitle); + await this.waitForElement(currencyConversionText); }); When(/^I click on remove wallet$/, async function () { - await this.click({ locator: '.removeWallet', method: 'css' }); + await this.click(removeWalletButton); }); When(/^I click on resync wallet$/, async function () { - await this.click({ locator: '.resyncButton', method: 'css' }); + await this.click(resyncWalletButton); }); When(/^I click on export wallet$/, async function () { - await this.click({ locator: '.exportWallet', method: 'css' }); + await this.click(exportButton); }); Then(/^I should see the wallet export for key "([^"]*)"$/, async function (expectedKey) { - await this.waitForElement({ locator: '.ExportPublicKeyDialog_component', method: 'css' }); + await this.waitForElement(exportPublicKeyDialog); const publicKeyForm = await this.driver.findElement(By.css('.CodeBlock_component')); const publicKey = await publicKeyForm.getText(); expect(publicKey).to.equal(expectedKey); }); Then(/^I click on the checkbox$/, async function () { - const warningCheckboxElement = await this.driver.findElement(By.css('.DangerousActionDialog_checkbox')); + const warningCheckboxElement = await this.driver.findElement( + By.css('.DangerousActionDialog_checkbox') + ); const checkbox = await warningCheckboxElement.findElement(By.xpath('//input[@type="checkbox"]')); await checkbox.click(); }); Then(/^I should see a no wallet message$/, async function () { - const noWalletMessage = await i18n.formatMessage( - this.driver, - { id: 'wallet.nowallet.title' } - ); - await this.waitUntilText({ locator: '.FullscreenMessage_title', method: 'css' }, noWalletMessage); + const noWalletMessage = await i18n.formatMessage(this.driver, { id: 'wallet.nowallet.title' }); + await this.waitUntilText(fullScreenMessage, noWalletMessage); }); Then(/^I sleep for ([^"]*)$/, async function (ms) { await this.driver.sleep(Number.parseInt(ms, 10)); }); -Then(/^I should see "Incorrect wallet password." error message$/, async function(){ - const errorSelector = '.ChangeWalletPasswordDialog_error'; - await this.waitUntilText( - { locator: errorSelector, method: 'css' }, - 'Incorrect wallet password.', - 15000 - ); -}); \ No newline at end of file +Then(/^I should see "Incorrect wallet password." error message$/, async function () { + await this.waitUntilText(changePasswordDialogError, 'Incorrect wallet password.', 15000); +}); diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index 18a7df5f40..c95c4e685a 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -12,6 +12,7 @@ import { defaultAssets, } from '../../app/api/ada/lib/storage/database/prepackaged/networks'; import { walletSummaryBox } from '../pages/walletTransactionsPage'; +import { amountInput, receiverInput } from '../pages/walletSendPage'; Given(/^I have a wallet with funds$/, async function () { const amountWithCurrency = await this.driver.findElements( @@ -35,13 +36,13 @@ When(/^I select the asset "(.+)" on the form$/, async function (assetName) { When(/^I fill the form:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: "input[name='receiver']", method: 'css' }, fields.address); - await this.input({ locator: "input[name='amount']", method: 'css' }, fields.amount); + await this.input(receiverInput, fields.address); + await this.input(amountInput, fields.amount); }); When(/^I fill the address of the form:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: "input[name='receiver']", method: 'css' }, fields.address); + await this.input(receiverInput, fields.address); }); Given(/^The expected transaction is "([^"]*)"$/, base64Tx => { @@ -75,7 +76,7 @@ When(/^I see CONFIRM TRANSACTION Pop up:$/, async function (table) { }); When(/^I clear the receiver$/, async function () { - await this.clearInput({ locator: "input[name='receiver']", method: 'css' }); + await this.clearInput(receiverInput); }); When(/^I clear the wallet password ([^"]*)$/, async function (password) { @@ -86,7 +87,7 @@ When(/^I clear the wallet password ([^"]*)$/, async function (password) { }); When(/^I fill the receiver as "([^"]*)"$/, async function (receiver) { - await this.input({ locator: "input[name='receiver']", method: 'css' }, receiver); + await this.input(receiverInput, receiver); }); When(/^The transaction fees are "([^"]*)"$/, async function (fee) { diff --git a/packages/yoroi-extension/features/step_definitions/trezor-steps.js b/packages/yoroi-extension/features/step_definitions/trezor-steps.js index 019a95ab7c..ec687af851 100644 --- a/packages/yoroi-extension/features/step_definitions/trezor-steps.js +++ b/packages/yoroi-extension/features/step_definitions/trezor-steps.js @@ -11,6 +11,7 @@ import { TrezorEmulatorController } from '../support/trezorEmulatorController'; import { expect } from 'chai'; import { verifyButton } from '../pages/verifyAddressPage'; import { testWallets } from '../mock-chain/TestWallets'; +import { errorBlockComponent } from '../pages/commonDialogPage'; export async function switchToTrezorAndAllow(customWorld: any) { // wait for a new tab @@ -89,5 +90,5 @@ Then(/^I verify the address on the trezor emulator$/, async function () { } await this.windowManager.waitForClosingAndSwitchTo(trezorConnectTabName, extensionTabName); // we should have this disable while the action is processing, but we don't show a spinner on this - await this.waitForElementNotPresent({ locator: '.ErrorBlock_component', method: 'css' }); + await this.waitForElementNotPresent(errorBlockComponent); }); diff --git a/packages/yoroi-extension/features/step_definitions/uri-steps.js b/packages/yoroi-extension/features/step_definitions/uri-steps.js index 13c50c36a9..d01c9f927f 100644 --- a/packages/yoroi-extension/features/step_definitions/uri-steps.js +++ b/packages/yoroi-extension/features/step_definitions/uri-steps.js @@ -4,6 +4,7 @@ import { When, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import { truncateAddress, } from '../../app/utils/formatters'; +import { amountInput } from '../pages/walletSendPage'; When(/^I click on "generate payment URL" button$/, async function () { await this.click({ locator: '.WalletReceive_generateURIIcon', method: 'css' }); @@ -11,7 +12,7 @@ When(/^I click on "generate payment URL" button$/, async function () { }); Then(/^I generate a URI for ([0-9]+) ADA$/, async function (amount) { - await this.input({ locator: "input[name='amount']", method: 'css' }, amount); + await this.input(amountInput, amount); await this.click({ locator: '.URIGenerateDialog_component .MuiButton-primary', method: 'css' }); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js index 5c9234b8cc..b4b98ab4d6 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js @@ -11,6 +11,7 @@ import { pickUpCurrencyDialog } from '../pages/newWalletPages'; import { continueButton } from '../pages/basicSetupPage'; +import { dialogTitle } from '../pages/commonDialogPage'; When(/^I click the create button$/, async function () { await this.click(createWalletButton); @@ -117,7 +118,7 @@ Then(/^I see All selected words are cleared$/, async function () { Then(/^I should stay in the create wallet dialog$/, async function () { const createMessage = await i18n.formatMessage(this.driver, { id: 'wallet.create.dialog.title' }); - await this.waitUntilText({ locator: '.dialog__title', method: 'css' }, createMessage.toUpperCase(), 2000); + await this.waitUntilText(dialogTitle, createMessage.toUpperCase(), 2000); }); Then( diff --git a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js index b5bbbd0dfe..0b7ebd054b 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js @@ -5,6 +5,7 @@ import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import { truncateAddress, } from '../../app/utils/formatters'; import { enterRecoveryPhrase } from '../support/helpers/helpers'; +import { primaryButton } from '../pages/commonDialogPage'; // ========== Paper wallet ========== @@ -17,7 +18,7 @@ Then(/^I select 2 addresses$/, async function () { }); Then(/^I click the create paper wallet button$/, async function () { - await this.click({ locator: '.primary', method: 'css' }); + await this.click(primaryButton); }); const fakeAddresses = [ diff --git a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js index 687be13eab..e2bea7a8b6 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js @@ -8,6 +8,7 @@ import { checkErrorByTranslationId, getPlates } from './common-steps'; import { enterRecoveryPhrase } from '../support/helpers/helpers'; import { cleanRecoverInput, + confirmButton, errorInvalidRecoveryPhrase, getWords, paperPasswordInput, @@ -17,6 +18,7 @@ import { } from '../pages/restoreWalletPage'; import { masterKeyInput } from '../pages/walletClaimTransferPage'; import { pickUpCurrencyDialog, pickUpCurrencyDialogCardano, restoreNormalWallet, shelleyEraButton, walletRestoreDialog, walletRestoreOptionDialog } from '../pages/newWalletPages'; +import { dialogTitle } from '../pages/commonDialogPage'; When(/^I click the restore button for ([^"]*)$/, async function (currency) { await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); @@ -149,7 +151,7 @@ Then(/^I should stay in the restore wallet dialog$/, async function () { const restoreMessage = await i18n.formatMessage(this.driver, { id: 'wallet.restore.dialog.title.label', }); - await this.waitUntilText({ locator: '.dialog__title', method: 'css' }, restoreMessage.toUpperCase(), 2000); + await this.waitUntilText(dialogTitle, restoreMessage.toUpperCase(), 2000); }); Then(/^I delete recovery phrase by clicking "x" signs$/, async function () { @@ -201,5 +203,5 @@ Then(/^I should see the wallet already exist window$/, async function () { }); When(/^I click the Open wallet button$/, async function () { - await this.click({ locator: '.confirmButton', method: 'css' }); + await this.click(confirmButton); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-steps.js index a9a1b9e5b7..fc21b063ff 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-steps.js @@ -2,14 +2,15 @@ import { When, Then } from 'cucumber'; import { truncateLongName } from '../../app/utils/formatters'; +import { walletNameInput } from '../pages/restoreWalletPage'; import { walletNameText } from '../pages/walletPage'; When(/^I enter the name "([^"]*)"$/, async function (walletName) { - await this.input({ locator: "input[name='walletName']", method: 'css' }, walletName); + await this.input(walletNameInput, walletName); }); When(/^I clear the name "([^"]*)"$/, async function (walletName) { - await this.clearInputUpdatingForm({ locator: "input[name='walletName']", method: 'css' }, walletName.length); + await this.clearInputUpdatingForm(walletNameInput, walletName.length); }); When(/^I navigate to wallet sidebar category$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js b/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js index 3e17acce60..829898338e 100644 --- a/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js +++ b/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js @@ -14,6 +14,7 @@ import { } from '../support/helpers/transfer-helpers'; import { claimTransferTab } from '../pages/walletPage'; import { byronButton } from '../pages/walletClaimTransferPage'; +import { primaryButton } from '../pages/commonDialogPage'; async function confirmAttentionScreen(customWorld: Object){ // Attention screen @@ -71,7 +72,7 @@ Then(/^I see the transfer transaction$/, async function () { await this.waitForElement({ locator: '.TransferSummaryPage_body', method: 'css' }); }); Then(/^I accept the prompt$/, async function () { - await this.click({ locator: '.primary', method: 'css' }); + await this.click(primaryButton); }); Then(/^I select the trezor option$/, async function () { await this.click({ locator: '.fromTrezor_connectTrezor', method: 'css' }); From 6291a3abaf383efc9eb5441df783d4ea79a42084 Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Tue, 30 Aug 2022 15:48:32 -0300 Subject: [PATCH 066/199] Moved more locators to corresponding pages --- .../features/pages/commonDialogPage.js | 7 +- .../features/pages/confirmTransactionPage.js | 11 +- .../features/pages/daedalusTransferPage.js | 17 +- .../features/pages/errorPage.js | 6 +- .../features/pages/mainWindowPage.js | 9 +- .../features/pages/newWalletPages.js | 172 ++++++++++++-- .../features/pages/restoreWalletPage.js | 30 +-- .../features/pages/settingsPage.js | 53 +++-- .../features/pages/sidebarPage.js | 22 +- .../features/pages/trezorConnectPage.js | 2 +- .../features/pages/uriPromptPage.js | 2 +- .../features/pages/verifyAddressPage.js | 10 +- .../features/pages/walletClaimTransferPage.js | 63 ++++- .../features/pages/walletDashboardPage.js | 5 +- .../features/pages/walletDelegationPage.js | 30 +++ .../features/pages/walletPage.js | 54 +++-- .../features/pages/walletReceivePage.js | 68 +++++- .../features/pages/walletSendPage.js | 77 ++++++- .../features/pages/walletTransactionsPage.js | 38 +++- .../features/pages/walletVotingPage.js | 48 ++++ .../features/pages/walletsListPage.js | 8 +- .../features/step_definitions/common-steps.js | 11 +- .../step_definitions/transactions-steps.js | 107 +++++---- .../step_definitions/tx-history-steps.js | 215 +++++++++--------- .../features/step_definitions/uri-steps.js | 29 +-- .../features/step_definitions/voting-steps.js | 66 ++++-- .../step_definitions/wallet-creation-steps.js | 79 ++++--- .../wallet-delegation-steps.js | 18 +- .../step_definitions/wallet-paper-steps.js | 29 +-- .../wallet-restoration-steps.js | 44 ++-- .../features/step_definitions/wallet-steps.js | 10 +- .../step_definitions/yoroi-transfer-steps.js | 103 +++++---- 32 files changed, 1007 insertions(+), 436 deletions(-) create mode 100644 packages/yoroi-extension/features/pages/walletDelegationPage.js create mode 100644 packages/yoroi-extension/features/pages/walletVotingPage.js diff --git a/packages/yoroi-extension/features/pages/commonDialogPage.js b/packages/yoroi-extension/features/pages/commonDialogPage.js index 242f03173f..4c1c6b8fa0 100644 --- a/packages/yoroi-extension/features/pages/commonDialogPage.js +++ b/packages/yoroi-extension/features/pages/commonDialogPage.js @@ -3,5 +3,8 @@ import type { LocatorObject } from '../support/webdriver'; export const primaryButton: LocatorObject = { locator: '.primary', method: 'css' }; -export const errorBlockComponent: LocatorObject = { locator: '.ErrorBlock_component', method: 'css' }; -export const dialogTitle = { locator: '.dialog__title', method: 'css' }; +export const errorBlockComponent: LocatorObject = { + locator: '.ErrorBlock_component', + method: 'css', +}; +export const dialogTitle: LocatorObject = { locator: '.dialog__title', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/confirmTransactionPage.js b/packages/yoroi-extension/features/pages/confirmTransactionPage.js index 30284d7c3c..47eefd528e 100644 --- a/packages/yoroi-extension/features/pages/confirmTransactionPage.js +++ b/packages/yoroi-extension/features/pages/confirmTransactionPage.js @@ -1,5 +1,10 @@ // @flow -export const feeField = { locator: '.TransferSummaryPage_fees', method: 'css' }; -export const amountField = { locator: '.TransferSummaryPage_amount', method: 'css' }; -export const totalAmountField = { locator: '.TransferSummaryPage_totalAmount', method: 'css' }; \ No newline at end of file +import type { LocatorObject } from '../support/webdriver'; + +export const feeField: LocatorObject = { locator: '.TransferSummaryPage_fees', method: 'css' }; +export const amountField: LocatorObject = { locator: '.TransferSummaryPage_amount', method: 'css' }; +export const totalAmountField: LocatorObject = { + locator: '.TransferSummaryPage_totalAmount', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/daedalusTransferPage.js b/packages/yoroi-extension/features/pages/daedalusTransferPage.js index f04a4cfc80..ec44f968ea 100644 --- a/packages/yoroi-extension/features/pages/daedalusTransferPage.js +++ b/packages/yoroi-extension/features/pages/daedalusTransferPage.js @@ -2,7 +2,16 @@ import type { LocatorObject } from '../support/webdriver'; -export const nextButton: LocatorObject = { locator: "//button[contains(@label, 'Next')]", method: 'xpath' }; -export const backButton: LocatorObject = { locator: "//button[contains(@label, 'Back')]", method: 'xpath' }; -export const formFieldOverridesClassicError: LocatorObject = { locator: '.FormFieldOverridesClassic_error', method: 'css' }; -export const transferButton: LocatorObject = { locator: '.transferButton', method: 'css' }; \ No newline at end of file +export const nextButton: LocatorObject = { + locator: "//button[contains(@label, 'Next')]", + method: 'xpath', +}; +export const backButton: LocatorObject = { + locator: "//button[contains(@label, 'Back')]", + method: 'xpath', +}; +export const formFieldOverridesClassicError: LocatorObject = { + locator: '.FormFieldOverridesClassic_error', + method: 'css', +}; +export const transferButton: LocatorObject = { locator: '.transferButton', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/errorPage.js b/packages/yoroi-extension/features/pages/errorPage.js index 8ea7a5905a..e565d484a2 100644 --- a/packages/yoroi-extension/features/pages/errorPage.js +++ b/packages/yoroi-extension/features/pages/errorPage.js @@ -1,4 +1,6 @@ // @flow -export const errorPageTitle = { locator: '.ErrorPage_title', method: 'css' }; -export const errorMessage = { locator: '.ErrorPage_error', method: 'css' }; \ No newline at end of file +import type { LocatorObject } from '../support/webdriver'; + +export const errorPageTitle: LocatorObject = { locator: '.ErrorPage_title', method: 'css' }; +export const errorMessage: LocatorObject = { locator: '.ErrorPage_error', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/mainWindowPage.js b/packages/yoroi-extension/features/pages/mainWindowPage.js index c2e14ce0e2..7b58e5161a 100644 --- a/packages/yoroi-extension/features/pages/mainWindowPage.js +++ b/packages/yoroi-extension/features/pages/mainWindowPage.js @@ -3,5 +3,10 @@ import type { LocatorObject } from '../support/webdriver'; export const yoroiClassic: LocatorObject = { locator: '.YoroiClassic', method: 'css' }; -export const serverErrorBanner = { locator: '.ServerErrorBanner_serverError', method: 'css' }; -export const maintenanceBody = { locator: '.Maintenance_body', method: 'css' }; \ No newline at end of file +export const serverErrorBanner: LocatorObject = { + locator: '.ServerErrorBanner_serverError', + method: 'css', +}; +export const maintenanceBody: LocatorObject = { locator: '.Maintenance_body', method: 'css' }; + +export const myWalletsPage: LocatorObject = { locator: '.MyWallets_page', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/newWalletPages.js b/packages/yoroi-extension/features/pages/newWalletPages.js index 2be897e07a..d77ff91ffc 100644 --- a/packages/yoroi-extension/features/pages/newWalletPages.js +++ b/packages/yoroi-extension/features/pages/newWalletPages.js @@ -3,34 +3,170 @@ import type { LocatorObject } from '../support/webdriver'; export const connectHwButton: LocatorObject = { locator: '.WalletAdd_btnConnectHW', method: 'css' }; -export const createWalletButton: LocatorObject = { locator: '.WalletAdd_btnCreateWallet', method: 'css' }; -export const restoreWalletButton: LocatorObject = { locator: '.WalletAdd_btnRestoreWallet', method: 'css' }; +export const createWalletButton: LocatorObject = { + locator: '.WalletAdd_btnCreateWallet', + method: 'css', +}; +export const walletAddRestoreWalletButton: LocatorObject = { + locator: '.WalletAdd_btnRestoreWallet', + method: 'css', +}; // Currency options dialog -export const pickUpCurrencyDialog: LocatorObject = { locator: '.PickCurrencyOptionDialog', method: 'css' }; -export const pickUpCurrencyDialogErgo: LocatorObject = { locator: '.PickCurrencyOptionDialog_ergo', method: 'css' }; -export const pickUpCurrencyDialogCardano: LocatorObject = { locator: '.PickCurrencyOptionDialog_cardano', method: 'css' }; -export const walletRestoreOptionDialog = { locator: '.WalletRestoreOptionDialog', method: 'css' }; -export const restoreNormalWallet = { locator: '.WalletRestoreOptionDialog_restoreNormalWallet', method: 'css' }; -export const walletRestoreDialog = { locator: '.WalletRestoreDialog', method: 'css' }; +export const pickUpCurrencyDialog: LocatorObject = { + locator: '.PickCurrencyOptionDialog', + method: 'css', +}; +export const pickUpCurrencyDialogErgo: LocatorObject = { + locator: '.PickCurrencyOptionDialog_ergo', + method: 'css', +}; +export const pickUpCurrencyDialogCardano: LocatorObject = { + locator: '.PickCurrencyOptionDialog_cardano', + method: 'css', +}; +export const walletRestoreOptionDialog: LocatorObject = { + locator: '.WalletRestoreOptionDialog', + method: 'css', +}; +export const restoreNormalWallet: LocatorObject = { + locator: '.WalletRestoreOptionDialog_restoreNormalWallet', + method: 'css', +}; +export const restore24WordWallet: LocatorObject = { + locator: '.WalletRestoreOptionDialog_normal24WordWallet', + method: 'css', +}; +export const walletRestoreDialog: LocatorObject = { + locator: '.WalletRestoreDialog', + method: 'css', +}; export const getCurrencyButton = (currency: string): LocatorObject => { - return { locator: `.PickCurrencyOptionDialog_${currency}`, method: 'css' }; + return { locator: `.PickCurrencyOptionDialog_${currency}`, method: 'css' }; }; // HW options dialog -export const hwOptionsDialog: LocatorObject = { locator: '.WalletConnectHWOptionDialog', method: 'css' }; -export const ledgerWalletButton: LocatorObject = { locator: '.WalletConnectHWOptionDialog_connectLedger', method: 'css' }; -export const trezorWalletButton: LocatorObject = { locator: '.WalletConnectHWOptionDialog_connectTrezor', method: 'css' }; +export const hwOptionsDialog: LocatorObject = { + locator: '.WalletConnectHWOptionDialog', + method: 'css', +}; +export const ledgerWalletButton: LocatorObject = { + locator: '.WalletConnectHWOptionDialog_connectLedger', + method: 'css', +}; +export const trezorWalletButton: LocatorObject = { + locator: '.WalletConnectHWOptionDialog_connectTrezor', + method: 'css', +}; // Era options dialog export const eraOptionsDialog: LocatorObject = { locator: '.WalletEraOptionDialog', method: 'css' }; -export const shelleyEraButton: LocatorObject = { locator: '.WalletEraOptionDialog_bgShelleyMainnet', method: 'css' }; -export const byronEraButton: LocatorObject = { locator: '.WalletEraOptionDialog_bgByronMainnet', method: 'css' }; +export const shelleyEraButton: LocatorObject = { + locator: '.WalletEraOptionDialog_bgShelleyMainnet', + method: 'css', +}; +export const byronEraButton: LocatorObject = { + locator: '.WalletEraOptionDialog_bgByronMainnet', + method: 'css', +}; // Trezor connect dialog export const trezorConnectDialog: LocatorObject = { locator: '.CheckDialog', method: 'css' }; -export const trezorWalletName: LocatorObject = { locator: '//input[@name="walletName"]', method: 'xpath' }; +export const trezorWalletName: LocatorObject = { + locator: '//input[@name="walletName"]', + method: 'xpath', +}; export const trezorConfirmButton: LocatorObject = { locator: '.MuiButton-primary', method: 'css' }; +// Create wallet dialog +export const createOptionDialog: LocatorObject = { + locator: '.WalletCreateOptionDialog', + method: 'css', +}; +export const createPaperWalletButton: LocatorObject = { + locator: '.WalletCreateOptionDialog_restorePaperWallet', + method: 'css', +}; +export const createWalletPasswordInput: LocatorObject = { + locator: '.WalletCreateDialog .walletPassword input', + method: 'css', +}; +export const createWalletRepeatPasswordInput: LocatorObject = { + locator: '.WalletCreateDialog .repeatedPassword input', + method: 'css', +}; +export const createPersonalWalletButton: LocatorObject = { + locator: '.WalletCreateDialog .primary', + method: 'css', +}; +export const createWalletPasswordHelperText: LocatorObject = { + locator: '//p[starts-with(@id, "walletPassword") and contains(@id, "-helper-text")]', + method: 'xpath', +}; +export const walletRecoveryPhraseMnemonicComponent: LocatorObject = { + locator: '.WalletRecoveryPhraseMnemonic_component', + method: 'css', +}; +export const createWalletNameError: LocatorObject = { + locator: '.walletName .MuiFormHelperText-root', + method: 'css', +}; +export const createWalletPasswordError: LocatorObject = { + locator: '.FormFieldOverridesClassic_error', + method: 'css', +}; +export const securityWarning: LocatorObject = { + locator: '.MuiFormControlLabel-root', + method: 'css', +}; + +// Recovery Phrase dialog +export const recoveryPhraseButton: LocatorObject = { + locator: '.WalletRecoveryPhraseDisplayDialog .primary', + method: 'css', +}; +export const recoveryPhraseConfirmButton: LocatorObject = { + locator: '//button[text()="Confirm"]', + method: 'xpath', +}; +export const clearButton: LocatorObject = { + locator: "//button[contains(text(), 'Clear')]", + method: 'xpath', +}; + +// Paper Wallet dialog +export const paperWalletDialogSelect: LocatorObject = { + locator: '.WalletPaperDialog_component .MuiSelect-select', + method: 'css', +}; +export const restorePaperWalletButton: LocatorObject = { + locator: '.WalletRestoreOptionDialog_restorePaperWallet', + method: 'css', +}; +export const restoreDialogButton: LocatorObject = { + locator: '.WalletRestoreDialog .primary', + method: 'css', +}; +export const recoveryPhraseDeleteIcon = { + locator: `(//span[contains(text(), '×')])[1]`, + method: 'xpath', +}; +export const recoveryPhraseError: LocatorObject = { + locator: '//p[starts-with(@id, "recoveryPhrase--")]', + method: 'xpath', +}; // Common elements -export const walletNameInput: LocatorObject = { locator: '//input[@name="walletName"]', method: 'xpath' }; +export const walletNameInput: LocatorObject = { + locator: '//input[@name="walletName"]', + method: 'xpath', +}; export const saveDialog: LocatorObject = { locator: '.SaveDialog', method: 'css' }; -export const saveButton: LocatorObject = { locator: '//button[@id="primaryButton"]', method: 'xpath' }; +export const saveButton: LocatorObject = { + locator: '//button[@id="primaryButton"]', + method: 'xpath', +}; export const checkDialog: LocatorObject = { locator: '.CheckDialog_component', method: 'css' }; -export const sendConfirmationDialog: LocatorObject = { locator: '.HWSendConfirmationDialog_dialog', method: 'css' }; \ No newline at end of file +export const sendConfirmationDialog: LocatorObject = { + locator: '.HWSendConfirmationDialog_dialog', + method: 'css', +}; +export const walletAlreadyExistsComponent: LocatorObject = { + locator: '.WalletAlreadyExistDialog_component', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/restoreWalletPage.js b/packages/yoroi-extension/features/pages/restoreWalletPage.js index b2a43a4fb7..26fc60ccde 100644 --- a/packages/yoroi-extension/features/pages/restoreWalletPage.js +++ b/packages/yoroi-extension/features/pages/restoreWalletPage.js @@ -2,33 +2,33 @@ import type { LocatorObject } from '../support/webdriver'; -export const errorInvalidRecoveryPhrase = { +export const errorInvalidRecoveryPhrase: LocatorObject = { locator: '//p[contains(@class, "-error") and contains(@id, "recoveryPhrase")]', - method: 'xpath' + method: 'xpath', }; -export const recoveryPhraseField = { +export const recoveryPhraseField: LocatorObject = { locator: '//input[starts-with(@id, "downshift-") and contains(@id, "-input")]', - method: 'xpath' + method: 'xpath', }; -export const proceedRecoveryButton = { +export const proceedRecoveryButton: LocatorObject = { locator: 'primaryButton', - method: 'id' + method: 'id', }; -export const cleanRecoverInput = { +export const cleanRecoverInput: LocatorObject = { locator: '.AutocompleteOverridesClassic_autocompleteWrapper input', - method: 'css' + method: 'css', }; export const getWords = (word: string): LocatorObject => { - return { locator: `//span[contains(text(), '${word}')]`, method: 'xpath' } + return { locator: `//span[contains(text(), '${word}')]`, method: 'xpath' }; }; -export const walletNameInput = { locator: "input[name='walletName']", method: 'css' }; -export const restoreWalletButton = { locator: '.WalletRestoreDialog .primary', method: 'css' } -export const walletPasswordInput = { locator: "input[name='walletPassword']", method: 'css' }; -export const repeatPasswordInput = { locator: "input[name='repeatPassword']", method: 'css' }; -export const paperPasswordInput = { locator: "input[name='paperPassword']", method: 'css' }; -export const confirmButton = { locator: '.confirmButton', method: 'css' }; \ No newline at end of file +export const walletNameInput: LocatorObject = { locator: "input[name='walletName']", method: 'css' }; +export const restoreWalletButton: LocatorObject = { locator: '.WalletRestoreDialog .primary', method: 'css' }; +export const walletPasswordInput: LocatorObject = { locator: "input[name='walletPassword']", method: 'css' }; +export const repeatPasswordInput: LocatorObject = { locator: "input[name='repeatPassword']", method: 'css' }; +export const paperPasswordInput: LocatorObject = { locator: "input[name='paperPassword']", method: 'css' }; +export const confirmButton: LocatorObject = { locator: '.confirmButton', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/settingsPage.js b/packages/yoroi-extension/features/pages/settingsPage.js index 7c32bd10e9..f4d4051fd2 100644 --- a/packages/yoroi-extension/features/pages/settingsPage.js +++ b/packages/yoroi-extension/features/pages/settingsPage.js @@ -2,26 +2,32 @@ import type { LocatorObject } from '../support/webdriver'; -export const fullScreenMessage = { locator: '.FullscreenMessage_title', method: 'css' }; +export const fullScreenMessage: LocatorObject = { + locator: '.FullscreenMessage_title', + method: 'css', +}; // Wallet tab -export const walletNameInputSelector = { +export const walletNameInputSelector: LocatorObject = { locator: '.SettingsLayout_settingsPane .walletName input', method: 'css', }; -export const walletNameInput = { +export const walletNameInput: LocatorObject = { locator: '.SettingsLayout_settingsPane .InlineEditingInput_component', method: 'css', }; -export const walletSettingsPane = { +export const walletSettingsPane: LocatorObject = { locator: '.SettingsLayout_settingsPane', method: 'css', }; -export const removeWalletButton = { locator: '.removeWallet', method: 'css' }; -export const resyncWalletButton = { locator: '.resyncButton', method: 'css' }; -export const exportButton = { locator: '.exportWallet', method: 'css' }; -export const exportPublicKeyDialog = { locator: '.ExportPublicKeyDialog_component', method: 'css' }; +export const removeWalletButton: LocatorObject = { locator: '.removeWallet', method: 'css' }; +export const resyncWalletButton: LocatorObject = { locator: '.resyncButton', method: 'css' }; +export const exportButton: LocatorObject = { locator: '.exportWallet', method: 'css' }; +export const exportPublicKeyDialog: LocatorObject = { + locator: '.ExportPublicKeyDialog_component', + method: 'css', +}; // Level of complexity tab @@ -60,38 +66,47 @@ export const repeatPasswordInput: LocatorObject = { locator: '.changePasswordDialog .repeatedPassword input', method: 'css', }; -export const confirmButton = { locator: '.confirmButton', method: 'css' }; -export const changePasswordDialog = { locator: '.changePasswordDialog', method: 'css' }; -export const walletPasswordHelperText = { +export const confirmButton: LocatorObject = { locator: '.confirmButton', method: 'css' }; +export const changePasswordDialog: LocatorObject = { + locator: '.changePasswordDialog', + method: 'css', +}; +export const walletPasswordHelperText: LocatorObject = { locator: '//p[starts-with(@id, "walletPassword--") and contains(@id, "-helper-text")]', method: 'xpath', }; -export const helperText = { locator: '.MuiFormHelperText-root', method: 'css' }; -export const changePasswordDialogError = { +export const helperText: LocatorObject = { locator: '.MuiFormHelperText-root', method: 'css' }; +export const changePasswordDialogError: LocatorObject = { locator: '.ChangeWalletPasswordDialog_error', method: 'css', }; // Support/Logs Tab -export const faqTitle = { +export const faqTitle: LocatorObject = { locator: "//h1[contains(text(), 'Frequently asked questions')]", method: 'xpath', }; -export const reportingAProblemTitle = { +export const reportingAProblemTitle: LocatorObject = { locator: "//h1[contains(text(), 'Reporting a problem')]", method: 'xpath', }; -export const logsTitle = { locator: "//h1[contains(text(), 'Logs')]", method: 'xpath' }; +export const logsTitle: LocatorObject = { + locator: "//h1[contains(text(), 'Logs')]", + method: 'xpath', +}; // Blockchain Tab -export const explorerSettingsDropdown = { locator: '.ExplorerSettings_component', method: 'css' }; -export const cardanoPaymentsURLTitle = { +export const explorerSettingsDropdown: LocatorObject = { + locator: '.ExplorerSettings_component', + method: 'css', +}; +export const cardanoPaymentsURLTitle: LocatorObject = { locator: "//h2[contains(text(), 'Cardano Payment URLs')]", method: 'xpath', }; -export const currencyConversionText = { +export const currencyConversionText: LocatorObject = { locator: "//h2[contains(text(), 'Currency Conversion')]", method: 'xpath', }; diff --git a/packages/yoroi-extension/features/pages/sidebarPage.js b/packages/yoroi-extension/features/pages/sidebarPage.js index 97e9554fa8..f8b8260680 100644 --- a/packages/yoroi-extension/features/pages/sidebarPage.js +++ b/packages/yoroi-extension/features/pages/sidebarPage.js @@ -1,13 +1,21 @@ // @flow // Revamped sidebar's elements -export const walletButton = { locator: 'settings.menu.wallet.link.label', method: 'id' }; -export const stakingButton = { locator: 'sidebar.staking', method: 'id' }; -export const assetsButton = { locator: 'sidebar.assets', method: 'id' }; -export const votingButton = { locator: 'sidebar.voting', method: 'id' }; -export const settingsButton = { locator: 'sidebar.settings', method: 'id' }; -export const faqButton = { locator: '.SidebarRevamp_faq', method: 'css' }; +import type { LocatorObject } from '../support/webdriver'; + +export const walletButton: LocatorObject = { + locator: 'settings.menu.wallet.link.label', + method: 'id', +}; +export const stakingButton: LocatorObject = { locator: 'sidebar.staking', method: 'id' }; +export const assetsButton: LocatorObject = { locator: 'sidebar.assets', method: 'id' }; +export const votingButton: LocatorObject = { locator: 'sidebar.voting', method: 'id' }; +export const settingsButton: LocatorObject = { locator: 'sidebar.settings', method: 'id' }; +export const faqButton: LocatorObject = { locator: '.SidebarRevamp_faq', method: 'css' }; // Classic version elements -export const walletButtonClassic = { locator: `//div[@class='Sidebar_categories']//button[1]`, method: 'xpath' }; \ No newline at end of file +export const walletButtonClassic: LocatorObject = { + locator: `//div[@class='Sidebar_categories']//button[1]`, + method: 'xpath', +}; diff --git a/packages/yoroi-extension/features/pages/trezorConnectPage.js b/packages/yoroi-extension/features/pages/trezorConnectPage.js index c925a7dd91..9eafe619b5 100644 --- a/packages/yoroi-extension/features/pages/trezorConnectPage.js +++ b/packages/yoroi-extension/features/pages/trezorConnectPage.js @@ -4,4 +4,4 @@ import type { LocatorObject } from '../support/webdriver'; export const dontAskAgainCheckbox: LocatorObject = { locator: '.custom-checkbox', method: 'css' }; export const confirmUsingTrezorButton: LocatorObject = { locator: '.confirm', method: 'css' }; -export const exportTrezorButton: LocatorObject = { locator: '.confirm', method: 'css' }; \ No newline at end of file +export const exportTrezorButton: LocatorObject = { locator: '.confirm', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/uriPromptPage.js b/packages/yoroi-extension/features/pages/uriPromptPage.js index 88de4cdbe8..ce2672b882 100644 --- a/packages/yoroi-extension/features/pages/uriPromptPage.js +++ b/packages/yoroi-extension/features/pages/uriPromptPage.js @@ -5,4 +5,4 @@ import type { LocatorObject } from '../support/webdriver'; export const uriPromptForm: LocatorObject = { locator: '.UriPromptForm_component', method: 'css' }; export const allowButton: LocatorObject = { locator: '.allowButton', method: 'css' }; export const uriAcceptComponent: LocatorObject = { locator: '.UriAccept_component', method: 'css' }; -export const finishButton: LocatorObject = { locator: '.finishButton', method: 'css' }; \ No newline at end of file +export const finishButton: LocatorObject = { locator: '.finishButton', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/verifyAddressPage.js b/packages/yoroi-extension/features/pages/verifyAddressPage.js index 947e8e14c2..26098679d3 100644 --- a/packages/yoroi-extension/features/pages/verifyAddressPage.js +++ b/packages/yoroi-extension/features/pages/verifyAddressPage.js @@ -2,6 +2,12 @@ import type { LocatorObject } from '../support/webdriver'; -export const verifyButton: LocatorObject = { locator: '.VerifyAddressDialog .primary', method: 'css' }; +export const verifyButton: LocatorObject = { + locator: '.VerifyAddressDialog .primary', + method: 'css', +}; export const addressField: LocatorObject = { locator: '.verificationAddress', method: 'css' }; -export const derivationField: LocatorObject = { locator: '.VerifyAddressDialog_derivation', method: 'css' }; \ No newline at end of file +export const derivationField: LocatorObject = { + locator: '.VerifyAddressDialog_derivation', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/walletClaimTransferPage.js b/packages/yoroi-extension/features/pages/walletClaimTransferPage.js index 938d2c1388..de1f3eaa13 100644 --- a/packages/yoroi-extension/features/pages/walletClaimTransferPage.js +++ b/packages/yoroi-extension/features/pages/walletClaimTransferPage.js @@ -1,6 +1,61 @@ // @flow -export const byronButton = { locator: '.TransferCards_byronEra', method: 'css' }; -export const daedalusMasterKeyButton = { locator: '.fromDaedalusMasterKey_masterKey', method: 'css' }; -export const twelveWordOption = { locator: '.fromDaedalusWallet12Word_legacyDaedalus', method: 'css' }; -export const masterKeyInput = { locator: 'input[name="masterKey"]', method: 'css' }; \ No newline at end of file +import type { LocatorObject } from '../support/webdriver'; + +export const byronButton: LocatorObject = { locator: '.TransferCards_byronEra', method: 'css' }; +export const daedalusMasterKeyButton: LocatorObject = { + locator: '.fromDaedalusMasterKey_masterKey', + method: 'css', +}; +export const twelveWordOption: LocatorObject = { + locator: '.fromDaedalusWallet12Word_legacyDaedalus', + method: 'css', +}; +export const masterKeyInput: LocatorObject = { locator: 'input[name="masterKey"]', method: 'css' }; +export const cancelTransferButton: LocatorObject = { locator: '.cancelTransferButton', method: 'css' }; +export const shelleyEraCard: LocatorObject = { locator: '.TransferCards_shelleyEra', method: 'css' }; +export const trezorOption: LocatorObject = { locator: '.fromTrezor_connectTrezor', method: 'css' }; +export const ledgerOption: LocatorObject = { locator: '.fromLedger_connectLedger', method: 'css' }; +export const yoroiPaperButton: LocatorObject = { locator: '.yoroiPaper', method: 'css' }; + +export const icarusTab: LocatorObject = { locator: '.IcarusTab', method: 'css' }; +export const restore15WordWalletIcarus: LocatorObject = { + locator: '.fromIcarusWallet15Word_restoreNormalWallet', + method: 'css', +}; +export const restoreShelley15WordDialog: LocatorObject = { + locator: '.ShelleyOptionDialog_restoreNormalWallet', + method: 'css', +}; +export const restoreShelleyPaperWalletDialog: LocatorObject = { + locator: '.ShelleyOptionDialog_restorePaperWallet', + method: 'css', +}; + +export const keyInput: LocatorObject = { locator: "input[name='key']", method: 'css' }; +export const descryptionPasswordInput: LocatorObject = { + locator: "input[name='decryptionPassword']", + method: 'css', +}; +export const shelleyPrivateKeyInput: LocatorObject = { locator: '.ShelleyOptionDialog_masterKey', method: 'css' }; +export const restoreIcarusPaperWalletOption: LocatorObject = { + locator: '.fromIcarusPaperWallet_restorePaperWallet', + method: 'css', +}; + +export const transferSummaryPage: LocatorObject = { locator: '.TransferSummaryPage_body', method: 'css' }; + +export const transferErrorPageTitle: LocatorObject = { locator: '.ErrorPage_title', method: 'css' }; +export const transferSuccessPageTitle: LocatorObject = { locator: '.SuccessPage_title', method: 'css' }; + +export const transferButton: LocatorObject = { locator: '.transferButton', method: 'css' }; +export const createYoroiWalletButton: LocatorObject = { + locator: '.createYoroiWallet.YoroiTransferStartPage_button', + method: 'css', +}; +export const transferSummaryPageError: LocatorObject = { locator: '.TransferSummaryPage_error', method: 'css' }; +export const keepRegisteredButton: LocatorObject = { + locator: `//button[contains(text(), "Keep registered")]`, + method: 'xpath', +}; +export const transferSummaryRefundText: LocatorObject = { locator: '.TransferSummaryPage_refund', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletDashboardPage.js b/packages/yoroi-extension/features/pages/walletDashboardPage.js index aa61194807..7b620cf796 100644 --- a/packages/yoroi-extension/features/pages/walletDashboardPage.js +++ b/packages/yoroi-extension/features/pages/walletDashboardPage.js @@ -5,5 +5,8 @@ import type { LocatorObject } from '../support/webdriver'; export const withdrawButton: LocatorObject = { locator: '.withdrawButton', method: 'css' }; export const rechartBar: LocatorObject = { locator: '.recharts-bar', method: 'css' }; -export const mangledWarningIcon: LocatorObject = { locator: '.UserSummary_mangledWarningIcon', method: 'css' }; +export const mangledWarningIcon: LocatorObject = { + locator: '.UserSummary_mangledWarningIcon', + method: 'css', +}; export const userSummaryLink: LocatorObject = { locator: '.UserSummary_link', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletDelegationPage.js b/packages/yoroi-extension/features/pages/walletDelegationPage.js new file mode 100644 index 0000000000..8cdb8b5f41 --- /dev/null +++ b/packages/yoroi-extension/features/pages/walletDelegationPage.js @@ -0,0 +1,30 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const delegationTxDialogError: LocatorObject = { + locator: '.DelegationTxDialog_error', + method: 'css', +}; +export const poolIdInput: LocatorObject = { locator: "input[name='poolId']", method: 'css' }; +export const stakePoolTicker: LocatorObject = { locator: '.StakePool_userTitle', method: 'css' }; +export const delegationFormNextButton: LocatorObject = { + locator: '.DelegationSendForm_component .MuiButton-primary', + method: 'css', +}; +export const delegationTxDialog: LocatorObject = { + locator: '.DelegationTxDialog_dialog', + method: 'css', +}; +export const delegationSuccessPage: LocatorObject = { + locator: '.SuccessPage_component', + method: 'css', +}; +export const delegationDashboardPageButton: LocatorObject = { + locator: "//button[contains(text(), 'Dashboard page')]", + method: 'xpath', +}; +export const delegationDashboardPage: LocatorObject = { + locator: '.StakingDashboard_page', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/walletPage.js b/packages/yoroi-extension/features/pages/walletPage.js index 5d74816da8..058d8b140c 100644 --- a/packages/yoroi-extension/features/pages/walletPage.js +++ b/packages/yoroi-extension/features/pages/walletPage.js @@ -1,18 +1,46 @@ // @flow // Revamped wallets list elements -export const summaryTab = { locator: 'summary', method: 'css' }; -export const sendTab = { locator: '.send', method: 'css' }; -export const receiveTab = { locator: '.receive', method: 'css' }; -export const claimTransferTab = { locator: '.claimTransfer', method: 'css' }; +import type { LocatorObject } from '../support/webdriver'; -export const walletNameText = { locator: '.NavPlate_name', method: 'css' }; -export const activeNavTab = { locator: '.WalletNavButton_active', method: 'css' }; -export const dashboardTab = { locator: '.stakeDashboard ', method: 'css' }; -export const transactionsTab = { locator: `//span[contains(text(), "Transactions")]`, method: 'xpath' }; +export const summaryTab: LocatorObject = { locator: 'summary', method: 'css' }; +export const sendTab: LocatorObject = { locator: '.send', method: 'css' }; +export const receiveTab: LocatorObject = { locator: '.receive', method: 'css' }; +export const claimTransferTab: LocatorObject = { locator: '.claimTransfer', method: 'css' }; +export const votingTab: LocatorObject = { locator: '.voting', method: 'css' }; +export const delegationByIdTab: LocatorObject = { locator: '.cardanoStake', method: 'css' }; -export const navDetailsAmount = { locator: '.NavWalletDetails_amount', method: 'css' }; -export const navDetailsHideButton = { locator: '.NavWalletDetails_toggleButton', method: 'css' }; -export const navDetailsWalletDropdown = { locator: '.NavDropdown_toggle', method: 'css' }; -export const navDetailsBuyButton = { locator: '.NavDropdownContent_buyButton', method: 'css' }; -export const buyDialogAddress = { locator: '.BuySellDialog_address', method: 'css' }; \ No newline at end of file +export const walletNameText: LocatorObject = { locator: '.NavPlate_name', method: 'css' }; +export const activeNavTab: LocatorObject = { locator: '.WalletNavButton_active', method: 'css' }; +export const dashboardTab: LocatorObject = { locator: '.stakeDashboard ', method: 'css' }; +export const transactionsTab: LocatorObject = { + locator: `//span[contains(text(), "Transactions")]`, + method: 'xpath', +}; + +export const navDetailsAmount: LocatorObject = { + locator: '.NavWalletDetails_amount', + method: 'css', +}; +export const navDetailsHideButton: LocatorObject = { + locator: '.NavWalletDetails_toggleButton', + method: 'css', +}; +export const navDetailsWalletDropdown: LocatorObject = { + locator: '.NavDropdown_toggle', + method: 'css', +}; +export const navDetailsBuyButton: LocatorObject = { + locator: '.NavDropdownContent_buyButton', + method: 'css', +}; +export const buyDialogAddress: LocatorObject = { locator: '.BuySellDialog_address', method: 'css' }; +export const addAdditionalWalletButton: LocatorObject = { + locator: `.NavBar_navbar .NavBar_content .MuiButton-primary`, + method: 'css', +}; + +export const walletNavBackButton: LocatorObject = { + locator: '.NavBar_navbar .NavBar_title .NavBarBack_backButton', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/walletReceivePage.js b/packages/yoroi-extension/features/pages/walletReceivePage.js index e87c696336..060259f236 100644 --- a/packages/yoroi-extension/features/pages/walletReceivePage.js +++ b/packages/yoroi-extension/features/pages/walletReceivePage.js @@ -16,11 +16,65 @@ export const getAddressLocator = (address: string): LocatorObject => { }; }; -export const addressErrorPhrase = { locator: '.StandardHeader_error', method: 'css' }; -export const generateAddressButton = { locator: '.generateAddressButton', method: 'css' }; -export const addressBookTab = { locator: `.addressBook`, method: 'css' }; -export const rewardAddressTab = { locator: `.reward`, method: 'css' }; -export const yourWalletAddrHeader = { locator: '.StandardHeader_copyableHash', method: 'css' }; -export const verifyAddressButton = { locator: '.WalletReceive_verifyIcon', method: 'css' }; +export const addressErrorPhrase: LocatorObject = { + locator: '.StandardHeader_error', + method: 'css', +}; +export const generateAddressButton: LocatorObject = { + locator: '.generateAddressButton', + method: 'css', +}; +export const addressBookTab: LocatorObject = { locator: `.addressBook`, method: 'css' }; +export const rewardAddressTab: LocatorObject = { locator: `.reward`, method: 'css' }; +export const yourWalletAddrHeader: LocatorObject = { + locator: '.StandardHeader_copyableHash', + method: 'css', +}; +export const verifyAddressButton: LocatorObject = { + locator: '.WalletReceive_verifyIcon', + method: 'css', +}; -export const verifyAddressHWButton = { locator: '.VerifyAddressDialog_component .primary', method: 'css' }; +export const verifyAddressHWButton: LocatorObject = { + locator: '.VerifyAddressDialog_component .primary', + method: 'css', +}; +export const unmangleButton: LocatorObject = { + locator: '.MangledHeader_submitButton ', + method: 'css', +}; + +export const generateUriIcon: LocatorObject = { + locator: '.WalletReceive_generateURIIcon', + method: 'css', +}; +export const uriGenerateDialog: LocatorObject = { locator: '.URIGenerateDialog', method: 'css' }; +export const generateUriButton: LocatorObject = { + locator: '.URIGenerateDialog_component .MuiButton-primary', + method: 'css', +}; +export const uriDisplayDialog: LocatorObject = { locator: '.URIDisplayDialog', method: 'css' }; +export const copyToClipboardIcon: LocatorObject = { + locator: '.URIDisplayDialog_uriDisplay .CopyableAddress_copyIconBig', + method: 'css', +}; +export const uriLandingDialog: LocatorObject = { locator: '.URILandingDialog', method: 'css' }; +export const uriLandingDialogAcceptButton: LocatorObject = { + locator: '.URILandingDialog .MuiButton-primary', + method: 'css', +}; + +export const uriVerifyDialog: LocatorObject = { locator: '.URIVerifyDialog', method: 'css' }; +export const uriVerifyDialogAddress: LocatorObject = { + locator: '.URIVerifyDialog_address', + method: 'css', +}; +export const uriVerifyDialogAmount: LocatorObject = { + locator: '.URIVerifyDialog_amount', + method: 'css', +}; +export const uriDetailsConfirmButton: LocatorObject = { + locator: '.URIVerifyDialog .primary', + method: 'css', +}; +export const invalidUriDialog: LocatorObject = { locator: '.URIInvalidDialog', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js index 8cbbeb2f7a..f2e601ffe6 100644 --- a/packages/yoroi-extension/features/pages/walletSendPage.js +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -1,6 +1,75 @@ // @flow -export const receiverInput = { locator: "input[name='receiver']", method: 'css' }; -export const amountInput = { locator: "input[name='amount']", method: 'css' }; -export const addMemoButton = { locator: '.addMemoButton', method: 'css' }; -export const memoContentInput = { locator: "input[name='memoContent']", method: 'css' }; +import type { LocatorObject } from '../support/webdriver'; + +export const assetSelector: LocatorObject = { + locator: '.WalletSendForm_component .SimpleInput_input', + method: 'css', +}; +export const assetListElement: LocatorObject = { + locator: '.TokenOptionRow_item_name', + method: 'css', +}; +export const receiverInput: LocatorObject = { locator: "input[name='receiver']", method: 'css' }; +export const amountInput: LocatorObject = { locator: "input[name='amount']", method: 'css' }; +export const addMemoButton: LocatorObject = { locator: '.addMemoButton', method: 'css' }; +export const memoContentInput: LocatorObject = { + locator: "input[name='memoContent']", + method: 'css', +}; +export const nextButton: LocatorObject = { + locator: '.WalletSendForm_component .MuiButton-primary', + method: 'css', +}; +export const invalidAddressError: LocatorObject = { + locator: '.receiver .SimpleInput_errored', + method: 'css', +}; +export const notEnoughAdaError: LocatorObject = { + locator: '.FormFieldOverridesClassic_error', + method: 'css', +}; + +export const sendAllCheckbox: LocatorObject = { + locator: '.WalletSendForm_checkbox', + method: 'css', +}; +export const sendMoneyConfirmationDialog: LocatorObject = { + locator: '.WalletSendConfirmationDialog_dialog', + method: 'css', +}; +export const submitButton: LocatorObject = { locator: '.confirmButton', method: 'css' }; +export const disabledSubmitButton: LocatorObject = { + locator: '.primary.SimpleButton_disabled', + method: 'css', +}; + +export const successPageTitle: LocatorObject = { locator: '.SuccessPage_title', method: 'css' }; +export const transactionPageButton: LocatorObject = { + locator: "//button[contains(text(), 'Transaction page')]", + method: 'xpath', +}; + +// Send confirmation Dialog + +export const sendConfirmationDialogAddressToText: LocatorObject = { + locator: '.WalletSendConfirmationDialog_addressTo', + method: 'css', +}; +export const sendConfirmationDialogFeesText: LocatorObject = { + locator: '.WalletSendConfirmationDialog_fees', + method: 'css', +}; +export const sendConfirmationDialogAmountText: LocatorObject = { + locator: '.WalletSendConfirmationDialog_amount', + method: 'css', +}; +export const sendConfirmationDialogTotalAmountText: LocatorObject = { + locator: '.WalletSendConfirmationDialog_totalAmount', + method: 'css', +}; +export const sendConfirmationDialogError: LocatorObject = { + locator: '.WalletSendConfirmationDialog_error', + method: 'css', +}; +export const warningBox: LocatorObject = { locator: '.WarningBox_warning', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletTransactionsPage.js b/packages/yoroi-extension/features/pages/walletTransactionsPage.js index 73041831de..d422c0bfb5 100644 --- a/packages/yoroi-extension/features/pages/walletTransactionsPage.js +++ b/packages/yoroi-extension/features/pages/walletTransactionsPage.js @@ -1,5 +1,37 @@ // @flow -export const walletSummaryBox = { locator: 'walletSummary_box', method: 'id' }; -export const walletSummaryComponent = { locator: "//div[@class='WalletSummary_component']", method: 'xpath' }; -export const copyToClipboardButton = { locator: '.CopyableAddress_copyIconBig', method: 'css' }; +import type { LocatorObject } from '../support/webdriver'; + +export const walletSummaryBox: LocatorObject = { locator: 'walletSummary_box', method: 'id' }; +export const walletSummaryComponent: LocatorObject = { + locator: '.WalletSummary_component', + method: 'css', +}; +export const copyToClipboardButton: LocatorObject = { + locator: '.CopyableAddress_copyIconBig', + method: 'css', +}; +export const numberOfTransactions: LocatorObject = { + locator: '.WalletSummary_numberOfTransactions', + method: 'css', +}; +export const noTransactionsComponent: LocatorObject = { + locator: '.WalletNoTransactions_component', + method: 'css', +}; +export const showMoreButton: LocatorObject = { + locator: '.WalletTransactionsList_component .MuiButton-primary', + method: 'css', +}; +export const transactionListElement: LocatorObject = { + locator: '.Transaction_component', + method: 'css', +}; +export const pendingTransactionElement: LocatorObject = { + locator: '.Transaction_pendingLabel', + method: 'css', +}; +export const failedTransactionElement: LocatorObject = { + locator: '.Transaction_failedLabel', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/walletVotingPage.js b/packages/yoroi-extension/features/pages/walletVotingPage.js new file mode 100644 index 0000000000..8398f80f8b --- /dev/null +++ b/packages/yoroi-extension/features/pages/walletVotingPage.js @@ -0,0 +1,48 @@ +// @flow + +import type { LocatorObject } from '../support/webdriver'; + +export const registerButton: LocatorObject = { + locator: '.Voting_registerButton > .primary', + method: 'css', +}; +export const generatePinDialog: LocatorObject = { + locator: '.GeneratePinDialog_dialog', + method: 'css', +}; +export const generatedPinButton: LocatorObject = { + locator: '.GeneratePinDialog_dialog > .Dialog_actions > .primary', + method: 'css', +}; +export const confirmPinDialog: LocatorObject = { + locator: '.ConfirmPinDialog_dialog', + method: 'css', +}; + +export const pinInput: LocatorObject = { locator: "input[name='pin']", method: 'css' }; +export const confirmPinButton: LocatorObject = { + locator: '.ConfirmPinDialog_dialog > .Dialog_actions > .primary', + method: 'css', +}; +export const confirmPinDialogError: LocatorObject = { + locator: + '.ConfirmPinDialog_dialog .ConfirmPinDialog_pinInputContainer .FormFieldOverridesClassic_error', + method: 'css', +}; + +export const registerDialog: LocatorObject = { locator: '.RegisterDialog_dialog', method: 'css' }; +export const registerDialogNextButton: LocatorObject = { + locator: '.RegisterDialog_dialog > .Dialog_actions > .primary', + method: 'css', +}; + +export const votingRegTxDialog: LocatorObject = { + locator: '.VotingRegTxDialog_dialog', + method: 'css', +}; +export const votingRegTxDialogError: LocatorObject = { + locator: '.VotingRegTxDialog_error', + method: 'css', +}; +export const qrCodeDialog: LocatorObject = { locator: '.QrCodeDialog_dialog', method: 'css' }; +export const errorBlock: LocatorObject = { locator: '.ErrorBlock_component > span', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletsListPage.js b/packages/yoroi-extension/features/pages/walletsListPage.js index bc282a8838..e0717ce66e 100644 --- a/packages/yoroi-extension/features/pages/walletsListPage.js +++ b/packages/yoroi-extension/features/pages/walletsListPage.js @@ -1,16 +1,18 @@ // @flow +import type { LocatorObject } from '../support/webdriver'; + import { WebElement } from 'selenium-webdriver'; import { getMethod } from '../support/helpers/helpers'; import { NoSuchElementError } from 'selenium-webdriver/lib/error'; -const walletRow = { locator: '.WalletRow_content', method: 'css' }; -const walletPlateNumber = { +const walletRow: LocatorObject = { locator: '.WalletRow_content', method: 'css' }; +const walletPlateNumber: LocatorObject = { locator: '.WalletRow_nameSection .NavPlate_wrapper .NavPlate_content .NavPlate_head .NavPlate_plate', method: 'css', }; -const walletButton = { +const walletButton: LocatorObject = { locator: '//button[@class="WalletRow_nameSection" and @type="button"]', method: 'xpath', }; diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index ba4da5c809..1f0bc77214 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -36,7 +36,6 @@ import { walletButton } from '../pages/sidebarPage'; import { getWalletButtonByPlate } from '../pages/walletsListPage'; import { connectHwButton, - restoreWalletButton, getCurrencyButton, pickUpCurrencyDialog, hwOptionsDialog, @@ -54,6 +53,7 @@ import { walletRestoreDialog, pickUpCurrencyDialogCardano, byronEraButton, + walletAddRestoreWalletButton, } from '../pages/newWalletPages'; import { allowPubKeysAndSwitchToYoroi, switchToTrezorAndAllow } from './trezor-steps'; import * as helpers from '../support/helpers/helpers'; @@ -62,6 +62,7 @@ import { extensionTabName } from '../support/windowManager'; import { confirmButton, repeatPasswordInput, + restoreWalletButton, walletPasswordInput, } from '../pages/restoreWalletPage'; import { walletNameText } from '../pages/walletPage'; @@ -317,7 +318,7 @@ Given(/^There is an Ergo wallet stored named ([^"]*)$/, async function (walletNa const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); - await this.click(restoreWalletButton); + await this.click(walletAddRestoreWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogErgo); @@ -335,7 +336,7 @@ Given(/^There is a Shelley wallet stored named ([^"]*)$/, async function (wallet const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); - await this.click(restoreWalletButton); + await this.click(walletAddRestoreWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); @@ -354,7 +355,7 @@ Given(/^There is a Byron wallet stored named ([^"]*)$/, async function (walletNa const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); - await this.click(restoreWalletButton); + await this.click(walletAddRestoreWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(getCurrencyButton('cardano')); @@ -372,7 +373,7 @@ Given(/^I have completed the basic setup$/, async function () { this.webDriverLogger.info(`Step: I have completed the basic setup`); // language select page await this.waitForElement(languageSelectionForm); - await this.click(); + await this.click(continueButton); // ToS page await this.waitForElement(termsOfUseComponent); const tosClassElement = await this.driver.findElement(By.css('.TermsOfUseForm_component')); diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index c95c4e685a..ed4c29fbda 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -11,8 +11,32 @@ import { networks, defaultAssets, } from '../../app/api/ada/lib/storage/database/prepackaged/networks'; -import { walletSummaryBox } from '../pages/walletTransactionsPage'; -import { amountInput, receiverInput } from '../pages/walletSendPage'; +import { walletSummaryBox, walletSummaryComponent } from '../pages/walletTransactionsPage'; +import { + amountInput, + assetListElement, + assetSelector, + disabledSubmitButton, + invalidAddressError, + nextButton, + notEnoughAdaError, + receiverInput, + sendAllCheckbox, + sendConfirmationDialogAddressToText, + sendConfirmationDialogAmountText, + sendConfirmationDialogError, + sendConfirmationDialogFeesText, + sendConfirmationDialogTotalAmountText, + sendMoneyConfirmationDialog, + submitButton, + successPageTitle, + transactionPageButton, + warningBox, +} from '../pages/walletSendPage'; +import { sendTab } from '../pages/walletPage'; +import { walletPasswordInput } from '../pages/restoreWalletPage'; +import { delegationTxDialogError } from '../pages/walletDelegationPage'; +import { unmangleButton } from '../pages/walletReceivePage'; Given(/^I have a wallet with funds$/, async function () { const amountWithCurrency = await this.driver.findElements( @@ -25,7 +49,7 @@ Given(/^I have a wallet with funds$/, async function () { }); When(/^I go to the send transaction screen$/, async function () { - await this.click({ locator: '.send', method: 'css' }); + await this.click(sendTab); }); When(/^I select the asset "(.+)" on the form$/, async function (assetName) { @@ -53,24 +77,15 @@ When(/^I see CONFIRM TRANSACTION Pop up:$/, async function (table) { const fields = table.hashes()[0]; const total = parseFloat(fields.amount) + parseFloat(fields.fee); - await this.waitUntilText( - { locator: '.WalletSendConfirmationDialog_addressTo', method: 'css' }, - truncateAddress(fields.address) - ); - await this.waitUntilContainsText( - { locator: '.WalletSendConfirmationDialog_fees', method: 'css' }, - fields.fee - ); - await this.waitUntilContainsText( - { locator: '.WalletSendConfirmationDialog_amount', method: 'css' }, - fields.amount - ); + await this.waitUntilText(sendConfirmationDialogAddressToText, truncateAddress(fields.address)); + await this.waitUntilContainsText(sendConfirmationDialogFeesText, fields.fee); + await this.waitUntilContainsText(sendConfirmationDialogAmountText, fields.amount); const network = networks.CardanoMainnet; const assetInfo = defaultAssets.filter(asset => asset.NetworkId === network.NetworkId)[0]; const decimalPlaces = assetInfo.Metadata.numberOfDecimals; await this.waitUntilContainsText( - { locator: '.WalletSendConfirmationDialog_totalAmount', method: 'css' }, + sendConfirmationDialogTotalAmountText, total.toFixed(decimalPlaces) ); }); @@ -80,10 +95,7 @@ When(/^I clear the receiver$/, async function () { }); When(/^I clear the wallet password ([^"]*)$/, async function (password) { - await this.clearInputUpdatingForm( - { locator: "input[name='walletPassword']", method: 'css' }, - password.length - ); + await this.clearInputUpdatingForm(walletPasswordInput, password.length); }); When(/^I fill the receiver as "([^"]*)"$/, async function (receiver) { @@ -102,9 +114,8 @@ When(/^The transaction fees are "([^"]*)"$/, async function (fee) { }); When(/^I click on the next button in the wallet send form$/, async function () { - const button = '.WalletSendForm_component .MuiButton-primary'; - await this.waitForElement({ locator: button, method: 'css' }); - await this.click({ locator: button, method: 'css' }); + await this.waitForElement(nextButton); + await this.click(nextButton); /** * Sometimes out tests fail because clicking this button isn't triggering a dialog * However it works flawlessly both locally and on localci @@ -116,7 +127,7 @@ When(/^I click on the next button in the wallet send form$/, async function () { */ await this.driver.sleep(500); try { - await this.click({ locator: button, method: 'css' }); + await this.click(nextButton); } catch (e) { // if the first click succeeded, the second will throw an exception // saying that the button can't be clicked because a dialog is in the way @@ -124,32 +135,32 @@ When(/^I click on the next button in the wallet send form$/, async function () { }); When(/^I click on "Send all" checkbox$/, async function () { - await this.click({ locator: '.WalletSendForm_checkbox', method: 'css' }); + await this.click(sendAllCheckbox); }); When(/^I see send money confirmation dialog$/, async function () { - await this.waitForElement({ locator: '.WalletSendConfirmationDialog_dialog', method: 'css' }); + await this.waitForElement(sendMoneyConfirmationDialog); }); When(/^I enter the wallet password:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: "input[name='walletPassword']", method: 'css' }, fields.password); + await this.input(walletPasswordInput, fields.password); }); When(/^I submit the wallet send form$/, async function () { - await this.click({ locator: '.confirmButton', method: 'css' }); + await this.click(submitButton); }); Then(/^I should see the successfully sent page$/, async function () { - await this.waitForElement({ locator: '.SuccessPage_title', method: 'css' }); + await this.waitForElement(successPageTitle); }); Then(/^I click the transaction page button$/, async function () { - await this.click({ locator: "//button[contains(text(), 'Transaction page')]", method: 'xpath' }); + await this.click(transactionPageButton); }); Then(/^I should see the summary screen$/, async function () { - await this.waitForElement({ locator: '.WalletSummary_component', method: 'css' }); + await this.waitForElement(walletSummaryComponent); }); Then(/^Revamp. I should see the summary screen$/, async function () { @@ -157,48 +168,39 @@ Then(/^Revamp. I should see the summary screen$/, async function () { }); Then(/^I should see an invalid address error$/, async function () { - await this.waitForElement({ locator: '.receiver .SimpleInput_errored', method: 'css' }); + await this.waitForElement(invalidAddressError); }); Then(/^I should see a not enough ada error$/, async function () { const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.NotEnoughMoneyToSendError', }); - await this.waitUntilText( - { locator: '.FormFieldOverridesClassic_error', method: 'css' }, - errorMessage - ); + await this.waitUntilText(notEnoughAdaError, errorMessage); }); Then(/^I should not be able to submit$/, async function () { - await this.waitForElement({ locator: '.primary.SimpleButton_disabled', method: 'css' }); + await this.waitForElement(disabledSubmitButton); }); Then(/^I should see an invalid signature error message$/, async function () { const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.invalidWitnessError', }); - await this.waitUntilText( - { locator: '.WalletSendConfirmationDialog_error', method: 'css' }, - errorMessage - ); + await this.waitUntilText(sendConfirmationDialogError, errorMessage); }); Then(/^I should see an incorrect wallet password error message$/, async function () { const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.IncorrectPasswordError', }); - await this.waitUntilText( - { locator: '.WalletSendConfirmationDialog_error', method: 'css' }, - errorMessage - ); + await this.waitUntilText(sendConfirmationDialogError, errorMessage); }); Then(/^I should see an delegation incorrect wallet password error message$/, async function () { const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.IncorrectPasswordError', }); - await this.waitUntilText({ locator: '.DelegationTxDialog_error', method: 'css' }, errorMessage); + await this.waitUntilText(delegationTxDialogError, errorMessage); }); Then(/^A successful tx gets sent from my wallet from another client$/, () => { @@ -212,26 +214,23 @@ Then(/^A pending tx gets sent from my wallet from another client$/, () => { }); Then(/^I should see a warning block$/, async function () { - await this.waitForElement({ locator: '.WarningBox_warning', method: 'css' }); + await this.waitForElement(warningBox); }); Then(/^I should see no warning block$/, async function () { - await this.waitForElementNotPresent({ locator: '.WarningBox_warning', method: 'css' }); + await this.waitForElementNotPresent(warningBox); }); When(/^I click on the unmangle button$/, async function () { - await this.click({ locator: '.MangledHeader_submitButton ', method: 'css' }); + await this.click(unmangleButton); }); When(/^I open the token selection dropdown$/, async function () { - await this.click({ locator: '.WalletSendForm_component .SimpleInput_input', method: 'css' }); + await this.click(assetSelector); }); When(/^I select token "([^"]*)"$/, async function (tokenName) { - const tokenRows = await this.getElementsBy({ - locator: '.TokenOptionRow_item_name', - method: 'css', - }); + const tokenRows = await this.getElementsBy(assetListElement); for (const row of tokenRows) { const name = await row.getText(); if (name === tokenName) { diff --git a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js index 02ea1952b2..a9d2e0b907 100644 --- a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js +++ b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js @@ -5,10 +5,20 @@ import { By } from 'selenium-webdriver'; import chai from 'chai'; import moment from 'moment'; import i18n from '../support/helpers/i18n-helpers'; +import { failedTransactionElement, noTransactionsComponent, numberOfTransactions, pendingTransactionElement, showMoreButton, transactionListElement } from '../pages/walletTransactionsPage'; +import { summaryTab } from '../pages/walletPage'; function verifyAllTxsFields( - txType, txAmount, txTime, txStatus, txFee, txFromList, txToList, - txId, expectedTx, txConfirmations + txType, + txAmount, + txTime, + txStatus, + txFee, + txFromList, + txToList, + txId, + expectedTx, + txConfirmations ) { chai.expect(txType).to.equal(expectedTx.txType); chai.expect(txAmount.split(' ')[0]).to.equal(expectedTx.txAmount); @@ -41,71 +51,60 @@ When(/^I see the transactions summary$/, async function () { // sometimes this UI twitches on load when it starts fetching data from the server // sleep to avoid the twitch breaking the test await this.driver.sleep(500); - await this.waitForElement({ locator: '.WalletSummary_numberOfTransactions', method: 'css' }); + await this.waitForElement(numberOfTransactions); }); Then( /^I should see that the number of transactions is ([^"]*)$/, async function (expectedTxsNumber) { - const txsNumberMessage = await i18n.formatMessage(this.driver, - { id: 'wallet.summary.page.transactionsLabel' }); - await this.waitUntilText( - { locator: '.WalletSummary_numberOfTransactions', method: 'css' }, - txsNumberMessage + ': ' + expectedTxsNumber - ); + const txsNumberMessage = await i18n.formatMessage(this.driver, { + id: 'wallet.summary.page.transactionsLabel', + }); + await this.waitUntilText(numberOfTransactions, txsNumberMessage + ': ' + expectedTxsNumber); } ); - Then(/^I should see no transactions$/, async function () { - await this.waitForElement({ locator: '.WalletNoTransactions_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); + await this.waitForElement(noTransactionsComponent); + const actualTxsList = await this.getElementsBy(transactionListElement); chai.expect(actualTxsList.length).to.equal(0); }); -Then( - /^I should see ([^"]*) ([^"]*) transactions$/, - async function (txsNumber, txExpectedStatus) { - const txsAmount = parseInt(txsNumber, 10); - const showMoreLocator = '.WalletTransactionsList_component .MuiButton-primary'; +Then(/^I should see ([^"]*) ([^"]*) transactions$/, async function (txsNumber, txExpectedStatus) { + const txsAmount = parseInt(txsNumber, 10); - await this.driver.sleep(500); - // press the show more transaction button until all transactions are visible - for (let i = 1; i < txsAmount; i++) { - const buttonShowMoreExists = await this.checkIfExists({ locator: showMoreLocator, method: 'css' }); - if (!buttonShowMoreExists) { - break; - } - await this.click({ locator: showMoreLocator, method: 'css' }); - await this.driver.sleep(500); + await this.driver.sleep(500); + // press the show more transaction button until all transactions are visible + for (let i = 1; i < txsAmount; i++) { + const buttonShowMoreExists = await this.checkIfExists(showMoreButton); + if (!buttonShowMoreExists) { + break; } + await this.click(showMoreButton); + await this.driver.sleep(500); + } - const allTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); - const pendingTxsList = await this.getElementsBy({ locator: '.Transaction_pendingLabel', method: 'css' }); - const failedTxsList = await this.getElementsBy({ locator: '.Transaction_failedLabel', method: 'css' }); - if (txExpectedStatus === 'pending') { - chai.expect(pendingTxsList.length).to.equal(txsAmount); - return; - } - if (txExpectedStatus === 'failed') { - chai.expect(failedTxsList.length).to.equal(txsAmount); - return; - } - chai.expect(allTxsList.length - pendingTxsList.length - failedTxsList.length) - .to.equal(txsAmount); + const allTxsList = await this.getElementsBy(transactionListElement); + const pendingTxsList = await this.getElementsBy(pendingTransactionElement); + const failedTxsList = await this.getElementsBy(failedTransactionElement); + if (txExpectedStatus === 'pending') { + chai.expect(pendingTxsList.length).to.equal(txsAmount); + return; } -); + if (txExpectedStatus === 'failed') { + chai.expect(failedTxsList.length).to.equal(txsAmount); + return; + } + chai.expect(allTxsList.length - pendingTxsList.length - failedTxsList.length).to.equal(txsAmount); +}); -When( - /^I expand the top transaction$/, - async function () { - await this.waitForElement({ locator: '.Transaction_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); - const topTx = actualTxsList[0]; +When(/^I expand the top transaction$/, async function () { + await this.waitForElement(transactionListElement); + const actualTxsList = await this.getElementsBy(transactionListElement); + const topTx = actualTxsList[0]; - await topTx.click(); - } -); + await topTx.click(); +}); async function parseTxInfo(addressList) { const addressInfoRow = await addressList.findElements(By.css('.Transaction_addressItem')); @@ -120,65 +119,69 @@ async function parseTxInfo(addressList) { return result; } -Then( - /^I verify top transaction content ([^"]*)$/, - async function (walletName) { - await this.waitForElement({ locator: '.Transaction_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); - const topTx = actualTxsList[0]; - - let status = 'successful'; - { - const pending = await topTx.findElements(By.css('.Transaction_pendingLabel')); - const failed = await topTx.findElements(By.css('.Transaction_failedLabel')); - if (pending.length > 0) { - status = 'pending'; - } else if (failed.length > 0) { - status = 'failed'; - } +Then(/^I verify top transaction content ([^"]*)$/, async function (walletName) { + await this.waitForElement(transactionListElement); + const actualTxsList = await this.getElementsBy(transactionListElement); + const topTx = actualTxsList[0]; + + let status = 'successful'; + { + const pending = await topTx.findElements(By.css('.Transaction_pendingLabel')); + const failed = await topTx.findElements(By.css('.Transaction_failedLabel')); + if (pending.length > 0) { + status = 'pending'; + } else if (failed.length > 0) { + status = 'failed'; } + } - await topTx.click(); + await topTx.click(); - const txList = await topTx.findElements(By.css('.Transaction_addressList')); - const fromTxInfo = await parseTxInfo(txList[0]); - const toTxInfo = await parseTxInfo(txList[1]); + const txList = await topTx.findElements(By.css('.Transaction_addressList')); + const fromTxInfo = await parseTxInfo(txList[0]); + const toTxInfo = await parseTxInfo(txList[1]); - const txData = await topTx.getText(); - const txDataFields = txData.split('\n'); - const [txTime, txType, txStatus, txFee, txAmount] = txDataFields; + const txData = await topTx.getText(); + const txDataFields = txData.split('\n'); + const [txTime, txType, txStatus, txFee, txAmount] = txDataFields; - const expectedTx = displayInfo[walletName]; + const expectedTx = displayInfo[walletName]; - const txId = await (async () => { - const elem = await topTx.findElement(By.css('.txid')); - return await elem.getText(); - })(); - const txConfirmation = - status === 'successful' - ? await (async () => { + const txId = await (async () => { + const elem = await topTx.findElement(By.css('.txid')); + return await elem.getText(); + })(); + const txConfirmation = + status === 'successful' + ? await (async () => { const txConfirmationsCount = await topTx.findElement(By.css('.confirmationCount')); const txConfirmationParentElem = await txConfirmationsCount.findElement(By.xpath('./..')); return await txConfirmationParentElem.getText(); })() - : undefined; - - verifyAllTxsFields(txType, txAmount, txTime, txStatus, txFee, fromTxInfo, - toTxInfo, txId, expectedTx, txConfirmation); - } -); + : undefined; + + verifyAllTxsFields( + txType, + txAmount, + txTime, + txStatus, + txFee, + fromTxInfo, + toTxInfo, + txId, + expectedTx, + txConfirmation + ); +}); -Then( - /^The number of confirmations of the top tx is ([^"]*)$/, - async function (count) { - await this.waitForElement({ locator: '.Transaction_component', method: 'css' }); - const actualTxsList = await this.getElementsBy({ locator: '.Transaction_component', method: 'css' }); - const topTx = actualTxsList[0]; - const assuranceElem = await topTx.findElements(By.css('.confirmationCount')); - const confirmationCount = await assuranceElem[0].getText(); - chai.expect(confirmationCount).to.equal(count); - } -); +Then(/^The number of confirmations of the top tx is ([^"]*)$/, async function (count) { + await this.waitForElement(transactionListElement); + const actualTxsList = await this.getElementsBy(transactionListElement); + const topTx = actualTxsList[0]; + const assuranceElem = await topTx.findElements(By.css('.confirmationCount')); + const confirmationCount = await assuranceElem[0].getText(); + chai.expect(confirmationCount).to.equal(count); +}); const displayInfo = { 'many-tx-wallet': { @@ -186,9 +189,7 @@ const displayInfo = { txAmount: '-0.169999', txTime: '2019-04-21T15:13:33.000Z', txStatus: 'HIGH', - txFrom: [ - ['Ae2tdPwUPE...VWfitHfUM9', 'BYRON - INTERNAL', '-0.82 ADA'], - ], + txFrom: [['Ae2tdPwUPE...VWfitHfUM9', 'BYRON - INTERNAL', '-0.82 ADA']], txTo: [ ['Ae2tdPwUPE...iLjTnt34Aj', 'BYRON - EXTERNAL', '+0.000001 ADA'], ['Ae2tdPwUPE...BA7XbSMhKd', 'BYRON - INTERNAL', '+0.65 ADA'], @@ -202,12 +203,8 @@ const displayInfo = { txAmount: '-0.999999', txTime: '2019-04-20T23:14:52.000Z', txStatus: 'PENDING', - txFrom: [ - ['Ae2tdPwUPE...e1cT2aGdSJ', 'BYRON - EXTERNAL', '-1 ADA'], - ], - txTo: [ - ['Ae2tdPwUPE...sTrQfTxPVX', 'PROCESSING...', '+0.000001 ADA'] - ], + txFrom: [['Ae2tdPwUPE...e1cT2aGdSJ', 'BYRON - EXTERNAL', '-1 ADA']], + txTo: [['Ae2tdPwUPE...sTrQfTxPVX', 'PROCESSING...', '+0.000001 ADA']], txId: 'fa6f2c82fb511d0cc9c12a540b5fac6e5a9b0f288f2d140f909f981279e16fbe', txFee: '0.999999', }, @@ -216,9 +213,7 @@ const displayInfo = { txAmount: '-0.18', txTime: '2019-04-20T23:14:51.000Z', txStatus: 'FAILED', - txFrom: [ - ['Ae2tdPwUPE...gBfkkDNBNv', 'BYRON - EXTERNAL', '-1 ADA'], - ], + txFrom: [['Ae2tdPwUPE...gBfkkDNBNv', 'BYRON - EXTERNAL', '-1 ADA']], txTo: [ ['Ae2tdPwUPE...xJPmFzi6G2', 'ADDRESS BOOK', '+0.000001 ADA'], ['Ae2tdPwUPE...bL4UYPN3eU', 'BYRON - INTERNAL', '+0.82 ADA'], @@ -229,5 +224,5 @@ const displayInfo = { }; When(/^I go to the tx history screen$/, async function () { - await this.click({ locator: '.summary ', method: 'css' }); + await this.click(summaryTab); }); diff --git a/packages/yoroi-extension/features/step_definitions/uri-steps.js b/packages/yoroi-extension/features/step_definitions/uri-steps.js index d01c9f927f..b5cb6cdac6 100644 --- a/packages/yoroi-extension/features/step_definitions/uri-steps.js +++ b/packages/yoroi-extension/features/step_definitions/uri-steps.js @@ -5,23 +5,24 @@ import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import { truncateAddress, } from '../../app/utils/formatters'; import { amountInput } from '../pages/walletSendPage'; +import { copyToClipboardIcon, generateUriButton, generateUriIcon, invalidUriDialog, uriDetailsConfirmButton, uriDisplayDialog, uriGenerateDialog, uriLandingDialog, uriLandingDialogAcceptButton, uriVerifyDialog, uriVerifyDialogAddress, uriVerifyDialogAmount } from '../pages/walletReceivePage'; When(/^I click on "generate payment URL" button$/, async function () { - await this.click({ locator: '.WalletReceive_generateURIIcon', method: 'css' }); - await this.waitForElement({ locator: '.URIGenerateDialog', method: 'css' }); + await this.click(generateUriIcon); + await this.waitForElement(uriGenerateDialog); }); Then(/^I generate a URI for ([0-9]+) ADA$/, async function (amount) { await this.input(amountInput, amount); - await this.click({ locator: '.URIGenerateDialog_component .MuiButton-primary', method: 'css' }); + await this.click(generateUriButton); }); Then(/^I should see the URI displayed in a new dialog$/, async function () { - await this.waitForElement({ locator: '.URIDisplayDialog', method: 'css' }); + await this.waitForElement(uriDisplayDialog); }); Then(/^I click on the copy to clipboard icon$/, async function () { - await this.click({ locator: '.URIDisplayDialog_uriDisplay .CopyableAddress_copyIconBig', method: 'css' }); + await this.click(copyToClipboardIcon); }); When(/^I open a cardano URI for address (([^"]*)) and ([0-9]+) ADA$/, async function (address, amount) { @@ -33,27 +34,27 @@ When(/^I open a cardano URI for address (([^"]*)) and ([0-9]+) ADA$/, async func }); Then(/^I should see and accept a warning dialog$/, async function () { - await this.waitForElement({ locator: '.URILandingDialog', method: 'css' }); - await this.click({ locator: '.URILandingDialog .MuiButton-primary', method: 'css' }); + await this.waitForElement(uriLandingDialog); + await this.click(uriLandingDialogAcceptButton); }); Then(/^I should see a dialog with the transaction details$/, async function (table) { const fields = table.hashes()[0]; - await this.waitForElement({ locator: '.URIVerifyDialog', method: 'css' }); - await this.waitUntilContainsText({ locator: '.URIVerifyDialog_address', method: 'css' }, truncateAddress(fields.address)); - await this.waitUntilContainsText({ locator: '.URIVerifyDialog_amount', method: 'css' }, fields.amount); + await this.waitForElement(uriVerifyDialog); + await this.waitUntilContainsText(uriVerifyDialogAddress, truncateAddress(fields.address)); + await this.waitUntilContainsText(uriVerifyDialogAmount, fields.amount); }); When(/^I confirm the URI transaction details$/, async function () { - await this.click({ locator: '.URIVerifyDialog .primary', method: 'css' }); + await this.click(uriDetailsConfirmButton); }); Then(/^I should land on send wallet screen with prefilled parameters$/, async function (table) { const fields = table.hashes()[0]; const rxInput = await this.driver.findElement(By.xpath("//input[@name='receiver']")).getAttribute('value'); expect(rxInput).to.be.equal(fields.address); - const amountInput = await this.driver.findElement(By.xpath("//input[@name='amount']")).getAttribute('value'); - expect(amountInput).to.be.equal(fields.amount); + const sendAmountInput = await this.driver.findElement(By.xpath("//input[@name='amount']")).getAttribute('value'); + expect(sendAmountInput).to.be.equal(fields.amount); }); When(/^I open an invalid cardano URI$/, async function () { @@ -65,5 +66,5 @@ When(/^I open an invalid cardano URI$/, async function () { }); Then(/^I should see an "invalid URI" dialog$/, async function () { - await this.waitForElement({ locator: '.URIInvalidDialog', method: 'css' }); + await this.waitForElement(invalidUriDialog); }); diff --git a/packages/yoroi-extension/features/step_definitions/voting-steps.js b/packages/yoroi-extension/features/step_definitions/voting-steps.js index f1fefab173..d0fd1c934d 100644 --- a/packages/yoroi-extension/features/step_definitions/voting-steps.js +++ b/packages/yoroi-extension/features/step_definitions/voting-steps.js @@ -2,93 +2,111 @@ import { When, Then } from 'cucumber'; import { By, Key } from 'selenium-webdriver'; +import { votingTab } from '../pages/walletPage'; +import { + confirmPinButton, + confirmPinDialog, + confirmPinDialogError, + errorBlock, + generatedPinButton, + generatePinDialog, + pinInput, + qrCodeDialog, + registerButton, + registerDialog, + registerDialogNextButton, + votingRegTxDialog, + votingRegTxDialogError, +} from '../pages/walletVotingPage'; import i18n from '../support/helpers/i18n-helpers'; When(/^I go to the voting page$/, async function () { - await this.click({ locator: '.voting', method: 'css' }); + await this.click(votingTab); }); When(/^I click on the register button in the voting page$/, async function () { - await this.click({ locator: '.Voting_registerButton > .primary', method: 'css' }); + await this.click(registerButton); }); Then(/^I see the Auto generated Pin Steps$/, async function () { const elements = await this.driver.findElements(By.css('.GeneratePinDialog_pin > span ')); const pin = []; - for(const item of elements){ + for (const item of elements) { pin.push(await item.getText()); } this.pin = pin; - await this.waitForElement({ locator: '.GeneratePinDialog_dialog', method: 'css' }); + await this.waitForElement(generatePinDialog); }); When(/^I click next on the generated pin step$/, async function () { - await this.click({ locator: '.GeneratePinDialog_dialog > .Dialog_actions > .primary', method: 'css' }); + await this.click(generatedPinButton); }); Then(/^I see the confirm Pin step$/, async function () { - await this.waitForElement({ locator: '.ConfirmPinDialog_dialog', method: 'css' }); + await this.waitForElement(confirmPinDialog); }); Then(/^I enter the generated pin$/, async function () { const pin = this.pin.join(''); - await this.input({ locator: "input[name='pin']", method: 'css' }, pin); + await this.input(pinInput, pin); }); When(/^I click next on the confirm pin step$/, async function () { - await this.click({ locator: '.ConfirmPinDialog_dialog > .Dialog_actions > .primary', method: 'css' }); + await this.click(confirmPinButton); }); Then(/^I see register step with spending password$/, async function () { - await this.waitForElement({ locator: '.RegisterDialog_dialog', method: 'css' }); + await this.waitForElement(registerDialog); }); When(/^I click next on the register step$/, async function () { - await this.click({ locator: '.RegisterDialog_dialog > .Dialog_actions > .primary', method: 'css' }); + await this.click(registerDialogNextButton); }); Then(/^I see confirm transaction step$/, async function () { - await this.waitForElement({ locator: '.VotingRegTxDialog_dialog', method: 'css' }); + await this.waitForElement(votingRegTxDialog); }); Then(/^Then I see qr code step$/, async function () { - await this.waitForElement({ locator: '.QrCodeDialog_dialog', method: 'css' }); + await this.waitForElement(qrCodeDialog); }); Then(/^I enter the wrong pin$/, async function () { // use copy to avoid reversing original pin const pin = [...this.pin].reverse().join(''); - await this.input({ locator: "input[name='pin']", method: 'css' }, pin); + await this.input(pinInput, pin); }); Then(/^I see should see pin mismatch error$/, async function () { - const errorMessage = await i18n.formatMessage(this.driver, { id: 'global.errors.pinDoesNotMatch' }); + const errorMessage = await i18n.formatMessage(this.driver, { + id: 'global.errors.pinDoesNotMatch', + }); // following selector is used as the error is deeply nested - await this.waitUntilText( - { locator: '.ConfirmPinDialog_dialog .ConfirmPinDialog_pinInputContainer .FormFieldOverridesClassic_error', method: 'css' }, - errorMessage - ); + await this.waitUntilText(confirmPinDialogError, errorMessage); // clear the wrong pin at the end // we doing backspace 4 times for pin length of 4 // as .clear() does not update the react component value const input = this.driver.findElement(By.name('pin')); - for(let i = 1; i<=4; i++) { + for (let i = 1; i <= 4; i++) { input.sendKeys(Key.BACK_SPACE); } }); Then(/^I see incorrect wallet password dialog$/, async function () { - const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.IncorrectPasswordError' }); + const errorMessage = await i18n.formatMessage(this.driver, { + id: 'api.errors.IncorrectPasswordError', + }); - await this.waitUntilText({ locator: '.ErrorBlock_component > span', method: 'css' }, errorMessage); + await this.waitUntilText(errorBlock, errorMessage); }); - Then(/^I see incorrect wallet password error in transaction step$/, async function () { - const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.IncorrectPasswordError' }); + const errorMessage = await i18n.formatMessage(this.driver, { + id: 'api.errors.IncorrectPasswordError', + }); - await this.waitUntilText({ locator: '.VotingRegTxDialog_error', method: 'css' }, errorMessage); + await this.waitUntilText(votingRegTxDialogError, errorMessage); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js index b4b98ab4d6..69f3bd8cae 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js @@ -6,12 +6,26 @@ import i18n from '../support/helpers/i18n-helpers'; import { expect, assert } from 'chai'; import { checkErrorByTranslationId } from './common-steps'; import { + clearButton, + createOptionDialog, + createPaperWalletButton, + createPersonalWalletButton, createWalletButton, + createWalletNameError, + createWalletPasswordError, + createWalletPasswordHelperText, + createWalletPasswordInput, + createWalletRepeatPasswordInput, getCurrencyButton, - pickUpCurrencyDialog + pickUpCurrencyDialog, + recoveryPhraseButton, + recoveryPhraseConfirmButton, + securityWarning, + walletRecoveryPhraseMnemonicComponent, } from '../pages/newWalletPages'; import { continueButton } from '../pages/basicSetupPage'; import { dialogTitle } from '../pages/commonDialogPage'; +import { addAdditionalWalletButton } from '../pages/walletPage'; When(/^I click the create button$/, async function () { await this.click(createWalletButton); @@ -27,35 +41,36 @@ When(/^I select Create Wallet$/, async function () { await this.click(createWalletButton); }); When(/^I select Paper Wallet$/, async function () { - await this.waitForElement({ locator: '.WalletCreateOptionDialog', method: 'css' }); - await this.click({ locator: '.WalletCreateOptionDialog_restorePaperWallet', method: 'css' }); + await this.waitForElement(createOptionDialog); + await this.click(createPaperWalletButton); }); When(/^I enter the created wallet password:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: '.WalletCreateDialog .walletPassword input', method: 'css' }, fields.password); - await this.input({ locator: '.WalletCreateDialog .repeatedPassword input', method: 'css' }, fields.repeatedPassword); + await this.input(createWalletPasswordInput, fields.password); + await this.input(createWalletRepeatPasswordInput, fields.repeatedPassword); }); When(/^I clear the created wallet password ([^"]*)$/, async function (password) { - await this.clearInputUpdatingForm({ locator: '.WalletCreateDialog .walletPassword input', method: 'css' }, password.length); + await this.clearInputUpdatingForm(createWalletPasswordInput, password.length); }); When(/^I click the "Create personal wallet" button$/, async function () { - await this.click({ locator: '.WalletCreateDialog .primary', method: 'css' }); + await this.click(createPersonalWalletButton); }); Then(/^I should see the invalid password error message:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '//p[starts-with(@id, "walletPassword") and contains(@id, "-helper-text")]', method: 'xpath' }, - error); + await checkErrorByTranslationId(this, createWalletPasswordHelperText, error); }); Then(/^I see the submit button is disabled$/, async function () { - const dialogElement = await this.driver.findElement(By.xpath('//div[contains(@class, "Dialog")]')); - const disabledButton = await dialogElement.findElement(By.xpath('.//button[contains(@class, "primary")]')); + const dialogElement = await this.driver.findElement( + By.xpath('//div[contains(@class, "Dialog")]') + ); + const disabledButton = await dialogElement.findElement( + By.xpath('.//button[contains(@class, "primary")]') + ); const buttonState = await disabledButton.isEnabled(); expect(buttonState).to.be.false; }); @@ -72,34 +87,37 @@ When(/^I accept the creation terms$/, async function () { When(/^I copy and enter the displayed mnemonic phrase$/, async function () { const mnemonicElement = await this.waitElementTextMatches( /^.*$/, - { locator: '.WalletRecoveryPhraseMnemonic_component', method: 'css' } + walletRecoveryPhraseMnemonicComponent ); const mnemonic = await mnemonicElement.getText(); - await this.click({ locator: '.WalletRecoveryPhraseDisplayDialog .primary', method: 'css' }); + await this.click(recoveryPhraseButton); const recoveryPhrase = mnemonic.split(' '); for (let i = 0; i < recoveryPhrase.length; i++) { const word = recoveryPhrase[i]; // same word can occur many times, so we look for any copy of the desired word still unselected await this.click({ - locator: "(//button[contains(@class,'MnemonicWord_component') " + // any word + locator: + "(//button[contains(@class,'MnemonicWord_component') " + // any word ` and (text() = '${word}')])`, - method: 'xpath' }); + method: 'xpath', + }); } const checkboxes = await this.driver.findElements( By.xpath("//input[contains(@class,'PrivateSwitchBase-input')]") ); checkboxes.forEach(box => box.click()); - await this.click({ locator: '//button[text()="Confirm"]', method: 'xpath' }); + await this.click(recoveryPhraseConfirmButton); }); When(/^I enter random mnemonic phrase$/, async function () { - await this.click({ locator: '.WalletRecoveryPhraseDisplayDialog .primary', method: 'css' }); + await this.click(recoveryPhraseButton); for (let i = 15; i > 1; i--) { await this.click({ locator: `//div[@class='WalletRecoveryPhraseEntryDialog_words']//button[${i}]`, - method: 'xpath' }); + method: 'xpath', + }); } const words = await this.driver.findElement(By.css('.WalletRecoveryPhraseMnemonic_component')); words @@ -109,11 +127,11 @@ When(/^I enter random mnemonic phrase$/, async function () { }); Then(/^I click Clear button$/, async function () { - await this.click({ locator: "//button[contains(text(), 'Clear')]", method: 'xpath' }); + await this.click(clearButton); }); Then(/^I see All selected words are cleared$/, async function () { - await this.waitUntilText({ locator: '.WalletRecoveryPhraseMnemonic_component', method: 'css' }, '', 5000); + await this.waitUntilText(walletRecoveryPhraseMnemonicComponent, '', 5000); }); Then(/^I should stay in the create wallet dialog$/, async function () { @@ -125,29 +143,20 @@ Then( /^I should see "Wallet name requires at least 1 and at most 40 letters." error message:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '.walletName .MuiFormHelperText-root', method: 'css' }, - error); + await checkErrorByTranslationId(this, createWalletNameError, error); } ); Then(/^I should see "Invalid Password" error message:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '.FormFieldOverridesClassic_error', method: 'css' }, - error); + await checkErrorByTranslationId(this, createWalletPasswordError, error); }); Then(/^I see the security warning prior:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '.MuiFormControlLabel-root', method: 'css' }, - error); + await checkErrorByTranslationId(this, securityWarning, error); }); Then(/^I click to add an additional wallet$/, async function () { - await this.click({ locator: `.NavBar_navbar .NavBar_content .MuiButton-primary`, method: 'css' }); + await this.click(addAdditionalWalletButton); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js index b2d43721f7..6383f02f07 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js @@ -1,34 +1,36 @@ // @flow import { Given, When, Then } from 'cucumber'; +import { delegationDashboardPage, delegationDashboardPageButton, delegationFormNextButton, delegationSuccessPage, delegationTxDialog, poolIdInput, stakePoolTicker } from '../pages/walletDelegationPage'; +import { delegationByIdTab } from '../pages/walletPage'; When(/^I go to the delegation by id screen$/, async function () { - await this.click({ locator: '.cardanoStake', method: 'css' }); + await this.click(delegationByIdTab); }); When(/^I fill the delegation id form:$/, async function (table) { const fields = table.hashes()[0]; - await this.input({ locator: "input[name='poolId']", method: 'css' }, fields.stakePoolId); + await this.input(poolIdInput, fields.stakePoolId); }); Then(/^I see the stakepool ticker "([^"]*)"$/, async function (ticker) { - await this.waitUntilText({ locator: '.StakePool_userTitle', method: 'css' }, ticker); + await this.waitUntilText(stakePoolTicker, ticker); }); When(/^I click on the next button in the delegation by id$/, async function () { - await this.click({ locator: '.DelegationSendForm_component .MuiButton-primary', method: 'css' }); + await this.click(delegationFormNextButton); }); When(/^I see the delegation confirmation dialog$/, async function () { - await this.waitForElement({ locator: '.DelegationTxDialog_dialog', method: 'css' }); + await this.waitForElement(delegationTxDialog); }); Given(/^I click on see dashboard$/, async function () { - await this.waitForElement({ locator: '.SuccessPage_component', method: 'css' }); - await this.click({ locator: "//button[contains(text(), 'Dashboard page')]", method: 'xpath' }); + await this.waitForElement(delegationSuccessPage); + await this.click(delegationDashboardPageButton); }); When(/^I should see the dashboard screen$/, async function () { - await this.waitForElement({ locator: '.StakingDashboard_page', method: 'css' }); + await this.waitForElement(delegationDashboardPage); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js index 0b7ebd054b..5c204a9d6a 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js @@ -3,14 +3,15 @@ import { Given, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import { expect } from 'chai'; -import { truncateAddress, } from '../../app/utils/formatters'; +import { truncateAddress } from '../../app/utils/formatters'; import { enterRecoveryPhrase } from '../support/helpers/helpers'; import { primaryButton } from '../pages/commonDialogPage'; +import { paperWalletDialogSelect } from '../pages/newWalletPages'; // ========== Paper wallet ========== Then(/^I open Number of Adddresses selection dropdown$/, async function () { - await this.click({ locator: '.WalletPaperDialog_component .MuiSelect-select', method: 'css' }); + await this.click(paperWalletDialogSelect); }); Then(/^I select 2 addresses$/, async function () { @@ -27,31 +28,33 @@ const fakeAddresses = [ ]; Then(/^I enter the paper recovery phrase$/, async function () { /** - * Mnemomic is printed on the paper wallet and not present in the UI - * So we instead fetch the paper wallet from app memory - */ - const recoveryPhrase = await this.driver.executeScript(() => ( - window.yoroi.stores.substores.ada.paperWallets.paper.scrambledWords - )); + * Mnemomic is printed on the paper wallet and not present in the UI + * So we instead fetch the paper wallet from app memory + */ + const recoveryPhrase = await this.driver.executeScript( + () => window.yoroi.stores.substores.ada.paperWallets.paper.scrambledWords + ); await enterRecoveryPhrase(this, recoveryPhrase.join(' ')); }); Given(/^I swap the paper wallet addresses$/, async function () { // make sure 2 addresses we generated as expected - const addresses = await this.driver.executeScript(() => ( - window.yoroi.stores.substores.ada.paperWallets.paper.addresses - )); + const addresses = await this.driver.executeScript( + () => window.yoroi.stores.substores.ada.paperWallets.paper.addresses + ); expect(addresses.length).to.be.equal(2); // we swap out the generated addresses with fake ones to get a consistent UI for screenshots - await this.driver.executeScript((fakes) => { + await this.driver.executeScript(fakes => { window.yoroi.stores.substores.ada.paperWallets.paper.addresses = fakes; }, fakeAddresses); }); Then(/^I should see two addresses displayed$/, async function () { - const addressesElem = await this.driver.findElements(By.xpath("//span[contains(@class, 'RawHash_hash')]")); + const addressesElem = await this.driver.findElements( + By.xpath("//span[contains(@class, 'RawHash_hash')]") + ); expect(addressesElem.length).to.be.equal(fakeAddresses.length); for (let i = 0; i < fakeAddresses.length; i++) { const address = await addressesElem[i].getText(); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js index e2bea7a8b6..21862664c0 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js @@ -14,14 +14,29 @@ import { paperPasswordInput, recoveryPhraseField, repeatPasswordInput, + restoreWalletButton, walletPasswordInput, } from '../pages/restoreWalletPage'; import { masterKeyInput } from '../pages/walletClaimTransferPage'; -import { pickUpCurrencyDialog, pickUpCurrencyDialogCardano, restoreNormalWallet, shelleyEraButton, walletRestoreDialog, walletRestoreOptionDialog } from '../pages/newWalletPages'; +import { + byronEraButton, + pickUpCurrencyDialog, + pickUpCurrencyDialogCardano, + recoveryPhraseDeleteIcon, + recoveryPhraseError, + restore24WordWallet, + restoreDialogButton, + restoreNormalWallet, + restorePaperWalletButton, + shelleyEraButton, + walletAlreadyExistsComponent, + walletRestoreDialog, + walletRestoreOptionDialog, +} from '../pages/newWalletPages'; import { dialogTitle } from '../pages/commonDialogPage'; When(/^I click the restore button for ([^"]*)$/, async function (currency) { - await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); + await this.click(restoreWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click({ locator: `.PickCurrencyOptionDialog_${currency}`, method: 'css' }); @@ -40,7 +55,7 @@ Then(/^I select Shelley-era 15-word wallet$/, async function () { await this.waitForElement(walletRestoreDialog); }); Then(/^I select Shelley-era 24-word wallet$/, async function () { - await this.click({ locator: '.WalletRestoreOptionDialog_normal24WordWallet', method: 'css' }); + await this.click(restore24WordWallet); await this.waitForElement(walletRestoreDialog); }); @@ -50,14 +65,14 @@ Then(/^I select bip44 15-word wallet$/, async function () { }); When(/^I click the restore paper wallet button$/, async function () { - await this.click({ locator: '.WalletAdd_btnRestoreWallet', method: 'css' }); + await this.click(restoreWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); await this.waitForElement(walletRestoreOptionDialog); - await this.click({ locator: '.WalletRestoreOptionDialog_restorePaperWallet', method: 'css' }); + await this.click(restorePaperWalletButton); await this.waitForElement(walletRestoreDialog); }); @@ -123,7 +138,7 @@ When(/^I clear the restored wallet password ([^"]*)$/, async function (password) }); When(/^I click the "Restore Wallet" button$/, async function () { - await this.click({ locator: '.WalletRestoreDialog .primary', method: 'css' }); + await this.click(restoreDialogButton); }); Then(/^I should see an "Invalid recovery phrase" error message$/, async function () { @@ -157,7 +172,7 @@ Then(/^I should stay in the restore wallet dialog$/, async function () { Then(/^I delete recovery phrase by clicking "x" signs$/, async function () { const webElements = await this.driver.findElements(By.xpath(`//span[contains(text(), '×')]`)); for (let i = 0; i < webElements.length; i++) { - await this.click({ locator: `(//span[contains(text(), '×')])[1]`, method: 'xpath' }); + await this.click(recoveryPhraseDeleteIcon); } const expectedElements = await this.driver.findElements( By.xpath(`//span[contains(text(), '×')]`) @@ -168,10 +183,7 @@ Then(/^I delete recovery phrase by clicking "x" signs$/, async function () { Then(/^I should see an "Invalid recovery phrase" error message:$/, async function (data) { const expectedError = data.hashes()[0]; - await checkErrorByTranslationId( - this, - { locator: '//p[starts-with(@id, "recoveryPhrase--")]', method: 'xpath' }, - expectedError); + await checkErrorByTranslationId(this, recoveryPhraseError, expectedError); }); Then(/^I don't see last word of ([^"]*) in recovery phrase field$/, async function (table) { @@ -179,7 +191,7 @@ Then(/^I don't see last word of ([^"]*) in recovery phrase field$/, async functi const lastWord = words[words.length - 1]; await this.waitForElementNotPresent({ locator: `//span[contains(@class, 'SimpleAutocomplete') and contains(text(), "${lastWord}")]`, - method: 'xpath' + method: 'xpath', }); }); @@ -191,15 +203,11 @@ Then(/^I should see an "(\d{1,2}) words left" error message:$/, async function ( id: expectedError.message, values: { number: Number(number) }, }); - const errorSelector = '//p[starts-with(@id, "recoveryPhrase--")]'; - await this.waitUntilText( - { locator: errorSelector, method: 'xpath' }, - errorMessage, - 15000); + await this.waitUntilText(recoveryPhraseError, errorMessage, 15000); }); Then(/^I should see the wallet already exist window$/, async function () { - await this.waitForElement({ locator: '.WalletAlreadyExistDialog_component', method: 'css' }); + await this.waitForElement(walletAlreadyExistsComponent); }); When(/^I click the Open wallet button$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/wallet-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-steps.js index fc21b063ff..add95d7745 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-steps.js @@ -2,8 +2,10 @@ import { When, Then } from 'cucumber'; import { truncateLongName } from '../../app/utils/formatters'; +import { myWalletsPage } from '../pages/mainWindowPage'; import { walletNameInput } from '../pages/restoreWalletPage'; -import { walletNameText } from '../pages/walletPage'; +import { walletNameText, walletNavBackButton } from '../pages/walletPage'; +import { walletButtonClassic } from '../pages/sidebarPage'; When(/^I enter the name "([^"]*)"$/, async function (walletName) { await this.input(walletNameInput, walletName); @@ -14,7 +16,7 @@ When(/^I clear the name "([^"]*)"$/, async function (walletName) { }); When(/^I navigate to wallet sidebar category$/, async function () { - await this.click({ locator: `//div[@class='Sidebar_categories']//button[1]`, method: 'xpath' }); + await this.click(walletButtonClassic); await this.waitForElement(walletNameText); }); @@ -23,9 +25,9 @@ Then(/^I should see the opened wallet with name "([^"]*)"$/, async function (wal }); Then(/^I unselect the wallet$/, async function () { - await this.click({ locator: '.NavBar_navbar .NavBar_title .NavBarBack_backButton', method: 'css' }); + await this.click(walletNavBackButton); }); When(/^I am on the my wallets screen$/, async function () { - await this.waitForElement({ locator: '.MyWallets_page', method: 'css' }); + await this.waitForElement(myWalletsPage); }); diff --git a/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js b/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js index 829898338e..66e210e54a 100644 --- a/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js +++ b/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js @@ -2,10 +2,7 @@ import { Given, When, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; -import { - navigateTo, - waitUntilUrlEquals -} from '../support/helpers/route-helpers'; +import { navigateTo, waitUntilUrlEquals } from '../support/helpers/route-helpers'; import i18n from '../support/helpers/i18n-helpers'; import { checkAddressesRecoveredAreCorrect, @@ -13,13 +10,39 @@ import { checkWithdrawalAddressesRecoveredAreCorrect, } from '../support/helpers/transfer-helpers'; import { claimTransferTab } from '../pages/walletPage'; -import { byronButton } from '../pages/walletClaimTransferPage'; +import { + byronButton, + cancelTransferButton, + descryptionPasswordInput, + icarusTab, + keyInput, + shelleyPrivateKeyInput, + restore15WordWalletIcarus, + restoreShelley15WordDialog, + restoreShelleyPaperWalletDialog, + shelleyEraCard, + restoreIcarusPaperWalletOption, + transferSummaryPage, + trezorOption, + ledgerOption, + yoroiPaperButton, + transferErrorPageTitle, + transferButton, + transferSuccessPageTitle, + createYoroiWalletButton, + transferSummaryPageError, + keepRegisteredButton, + transferSummaryRefundText, +} from '../pages/walletClaimTransferPage'; import { primaryButton } from '../pages/commonDialogPage'; +import { fullScreenMessage } from '../pages/settingsPage'; -async function confirmAttentionScreen(customWorld: Object){ +async function confirmAttentionScreen(customWorld: Object) { // Attention screen await customWorld.waitForElement({ locator: '.HardwareDisclaimer_component', method: 'css' }); - const disclaimerClassElement = await customWorld.driver.findElement(By.css('.HardwareDisclaimer_component')); + const disclaimerClassElement = await customWorld.driver.findElement( + By.css('.HardwareDisclaimer_component') + ); const checkbox = await disclaimerClassElement.findElement(By.xpath('//input[@type="checkbox"]')); await checkbox.click(); await customWorld.click({ locator: '//button[text()="I understand"]', method: 'xpath' }); @@ -35,63 +58,64 @@ Given(/^Revamp. I go to the claim\/transfer page$/, async function () { }); When(/^I click skip the transfer$/, async function () { - await this.click({ locator: '.cancelTransferButton', method: 'css' }); + await this.click(cancelTransferButton); }); When(/^I click on the shelley button on the transfer screen$/, async function () { - await this.click({ locator: '.TransferCards_shelleyEra', method: 'css' }); + await this.click(shelleyEraCard); }); When(/^I click on the byron button on the transfer screen$/, async function () { await this.click(byronButton); }); Then(/^I click on the icarus tab$/, async function () { - await this.click({ locator: '.IcarusTab', method: 'css' }); + await this.click(icarusTab); }); Then(/^I select the Byron 15-word option$/, async function () { - await this.click({ locator: '.fromIcarusWallet15Word_restoreNormalWallet', method: 'css' }); + await this.click(restore15WordWalletIcarus); }); Then(/^I select the Shelley 15-word option$/, async function () { - await this.click({ locator: '.ShelleyOptionDialog_restoreNormalWallet', method: 'css' }); + await this.click(restoreShelley15WordDialog); }); Then(/^I select the Shelley paper wallet option$/, async function () { - await this.click({ locator: '.ShelleyOptionDialog_restorePaperWallet', method: 'css' }); + await this.click(restoreShelleyPaperWalletDialog); }); When(/^I enter the key "([^"]*)"$/, async function (password) { - await this.input({ locator: "input[name='key']", method: 'css' }, password); + await this.input(keyInput, password); }); When(/^I enter the decryption password "([^"]*)"$/, async function (password) { - await this.input({ locator: "input[name='decryptionPassword']", method: 'css' }, password); + await this.input(descryptionPasswordInput, password); }); Then(/^I select the private key option$/, async function () { - await this.click({ locator: '.ShelleyOptionDialog_masterKey', method: 'css' }); + await this.click(shelleyPrivateKeyInput); }); Then(/^I select the yoroi paper wallet option$/, async function () { - await this.click({ locator: '.fromIcarusPaperWallet_restorePaperWallet', method: 'css' }); + await this.click(restoreIcarusPaperWalletOption); }); Then(/^I see the transfer transaction$/, async function () { - await this.waitForElement({ locator: '.TransferSummaryPage_body', method: 'css' }); + await this.waitForElement(transferSummaryPage); }); Then(/^I accept the prompt$/, async function () { await this.click(primaryButton); }); Then(/^I select the trezor option$/, async function () { - await this.click({ locator: '.fromTrezor_connectTrezor', method: 'css' }); + await this.click(trezorOption); // Attention screen await confirmAttentionScreen(this); }); Then(/^I select the ledger option$/, async function () { - await this.click({ locator: '.fromLedger_connectLedger', method: 'css' }); + await this.click(ledgerOption); // Attention screen await confirmAttentionScreen(this); }); When(/^I click on the yoroiPaper button on the Yoroi Transfer start screen$/, async function () { - await this.click({ locator: '.yoroiPaper', method: 'css' }); + await this.click(yoroiPaperButton); }); Then(/^I should see the Yoroi transfer error screen$/, async function () { - const errorPageTitle = await i18n.formatMessage(this.driver, - { id: 'api.errors.generateTransferTxError' }); - await this.waitUntilText({ locator: '.ErrorPage_title', method: 'css' }, errorPageTitle); + const errorPageTitle = await i18n.formatMessage(this.driver, { + id: 'api.errors.generateTransferTxError', + }); + await this.waitUntilText(transferErrorPageTitle, errorPageTitle); }); Then(/^I should see on the Yoroi transfer summary screen:$/, async function (table) { @@ -109,41 +133,40 @@ Then(/^I should see on the Yoroi withdrawal transfer summary screen:$/, async fu }); When(/^I confirm Yoroi transfer funds$/, async function () { - await this.click({ locator: '.transferButton', method: 'css' }); + await this.click(transferButton); }); Then(/^I should see the Yoroi transfer success screen$/, async function () { - const successPageTitle = await i18n.formatMessage(this.driver, - { id: 'yoroiTransfer.successPage.title' }); - await this.waitUntilText({ locator: '.SuccessPage_title', method: 'css' }, successPageTitle.toUpperCase()); + const successPageTitle = await i18n.formatMessage(this.driver, { + id: 'yoroiTransfer.successPage.title', + }); + await this.waitUntilText(transferSuccessPageTitle, successPageTitle.toUpperCase()); }); Then(/^I should see the transfer screen disabled$/, async function () { - const noWalletMessage = await i18n.formatMessage( - this.driver, - { id: 'wallet.nowallet.title' } - ); - await this.waitUntilText({ locator: '.FullscreenMessage_title', method: 'css' }, noWalletMessage); + const noWalletMessage = await i18n.formatMessage(this.driver, { id: 'wallet.nowallet.title' }); + await this.waitUntilText(fullScreenMessage, noWalletMessage); }); Then(/^I should see the "CREATE YOROI WALLET" button disabled$/, async function () { - await this.waitDisable({ locator: '.createYoroiWallet.YoroiTransferStartPage_button', method: 'css' }); + await this.waitDisable(createYoroiWalletButton); }); Then(/^I should see wallet changed notice$/, async function () { - const walletChangedError = await i18n.formatMessage(this.driver, - { id: 'yoroiTransfer.error.walletChangedError' }); - await this.waitUntilText({ locator: '.TransferSummaryPage_error', method: 'css' }, walletChangedError); + const walletChangedError = await i18n.formatMessage(this.driver, { + id: 'yoroiTransfer.error.walletChangedError', + }); + await this.waitUntilText(transferSummaryPageError, walletChangedError); }); When(/^I keep the staking key$/, async function () { - await this.click({ locator: `//button[contains(text(), "Keep registered")]`, method: 'xpath' }); + await this.click(keepRegisteredButton); }); Then(/^I see the deregistration for the transaction$/, async function () { - await this.waitForElement({ locator: '.TransferSummaryPage_refund', method: 'css' }); + await this.waitForElement(transferSummaryRefundText); }); Then(/^I do not see the deregistration for the transaction$/, async function () { - await this.waitForElementNotPresent({ locator: '.TransferSummaryPage_refund', method: 'css' }); + await this.waitForElementNotPresent(transferSummaryRefundText); }); From 32444d339fda55de4c7de7e89fff8c14678628c6 Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Tue, 30 Aug 2022 16:46:48 -0300 Subject: [PATCH 067/199] Added changes to fix errors --- .../features/pages/walletDelegationPage.js | 2 + .../features/pages/walletSendPage.js | 2 +- .../addresses-generation-steps.js | 7 +-- .../step_definitions/connector-steps.js | 12 +++-- .../daedalus-transfer-steps.js | 43 ++++++++-------- .../general-settings-steps.js | 20 ++++++-- .../installation-procedure-steps.js | 7 +-- .../features/step_definitions/memo-steps.js | 8 ++- .../step_definitions/migration-steps.js | 2 +- .../step_definitions/select-language-steps.js | 7 +-- .../step_definitions/tx-history-steps.js | 9 +++- .../features/step_definitions/uri-steps.js | 50 ++++++++++++++----- .../wallet-delegation-steps.js | 11 +++- .../step_definitions/yoroi-transfer-steps.js | 5 +- 14 files changed, 124 insertions(+), 61 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletDelegationPage.js b/packages/yoroi-extension/features/pages/walletDelegationPage.js index 8cdb8b5f41..7ee25df7f6 100644 --- a/packages/yoroi-extension/features/pages/walletDelegationPage.js +++ b/packages/yoroi-extension/features/pages/walletDelegationPage.js @@ -28,3 +28,5 @@ export const delegationDashboardPage: LocatorObject = { locator: '.StakingDashboard_page', method: 'css', }; +export const hardwareDisclaimerComponent = { locator: '.HardwareDisclaimer_component', method: 'css' }; +export const understandButton = { locator: '//button[text()="I understand"]', method: 'xpath' }; diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js index f2e601ffe6..c266ef1e69 100644 --- a/packages/yoroi-extension/features/pages/walletSendPage.js +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -14,7 +14,7 @@ export const receiverInput: LocatorObject = { locator: "input[name='receiver']", export const amountInput: LocatorObject = { locator: "input[name='amount']", method: 'css' }; export const addMemoButton: LocatorObject = { locator: '.addMemoButton', method: 'css' }; export const memoContentInput: LocatorObject = { - locator: "input[name='memoContent']", + locator: "input[name='memo']", method: 'css', }; export const nextButton: LocatorObject = { diff --git a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js index eff4c477ac..d0e830cf71 100644 --- a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js @@ -14,7 +14,7 @@ import { getGeneratedAddressLocator, addressBookTab, rewardAddressTab, - yourWalletAddrHeader + yourWalletAddrHeader, } from '../pages/walletReceivePage'; Given(/^Revamp. I go to the receive screen$/, async function () { @@ -77,10 +77,7 @@ When(/^I click on the Generate new address button ([0-9]+) times$/, async functi }); Then(/^I should see my latest address "([^"]*)" at the top$/, async function (address) { - await this.waitUntilText( - yourWalletAddrHeader, - truncateAddress(address) - ); + await this.waitUntilText(yourWalletAddrHeader, truncateAddress(address)); }); Then(/^I should see at least ([^"]*) addresses$/, async function (numAddresses) { diff --git a/packages/yoroi-extension/features/step_definitions/connector-steps.js b/packages/yoroi-extension/features/step_definitions/connector-steps.js index 2467a87577..8a229ea2cf 100644 --- a/packages/yoroi-extension/features/step_definitions/connector-steps.js +++ b/packages/yoroi-extension/features/step_definitions/connector-steps.js @@ -276,7 +276,9 @@ Then(/^I close the dApp-connector pop-up window$/, async function () { }); Then(/^The wallet (.+) is connected to the website (.+)$/, async function (walletName, websiteUrl) { - this.webDriverLogger.info(`Step: The wallet ${walletName} is connected to the website ${websiteUrl}`); + this.webDriverLogger.info( + `Step: The wallet ${walletName} is connected to the website ${websiteUrl}` + ); await this.windowManager.switchTo(extensionTabName); const connectedWebsitesAddress = `${this.getExtensionUrl()}#/connector/connected-websites`; // it should be reworked by using ui components when it is done @@ -382,7 +384,9 @@ When(/^I ask to get Collateral for (.+) Utxos$/, async function (utxos) { Then( /^The dApp should see collateral: (.+) for (.+)$/, async function (expectedCollateral, utxosAmount) { - this.webDriverLogger.info(`Step: The dApp should see collateral: ${expectedCollateral} for ${utxosAmount}`); + this.webDriverLogger.info( + `Step: The dApp should see collateral: ${expectedCollateral} for ${utxosAmount}` + ); const collateral = await this.mockDAppPage.getCollateralUtxos(utxosAmount); const collateralJson = JSON.parse(collateral)[0]; const expectedUtxos = JSON.parse(expectedCollateral); @@ -422,7 +426,9 @@ Then(/^I should see the collateral from address info:$/, async function (table) addr => addr.address === expectedFromAddress && addr.amount === parseFloat(expectedFromAddressAmount) ); - this.webDriverLogger.info(`Step: I should see the collateral from address info: address: ${expectedFromAddress}, amount: ${expectedFromAddressAmount}`); + this.webDriverLogger.info( + `Step: I should see the collateral from address info: address: ${expectedFromAddress}, amount: ${expectedFromAddressAmount}` + ); expect( foundFromAddresses.length, `Expected fromAddress: diff --git a/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js b/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js index 8f51dfcb88..92de8c3f54 100644 --- a/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js +++ b/packages/yoroi-extension/features/step_definitions/daedalus-transfer-steps.js @@ -1,19 +1,16 @@ // @flow import { Before, Given, When, Then, After } from 'cucumber'; -import { - getMockServer, - closeMockServer -} from '../mock-chain/mockCardanoServer'; +import { getMockServer, closeMockServer } from '../mock-chain/mockCardanoServer'; import { getMockWebSocketServer, closeMockWebSocketServer, - mockRestoredDaedalusAddresses + mockRestoredDaedalusAddresses, } from '../mock-chain/mockWebSocketServer'; import i18n from '../support/helpers/i18n-helpers'; import { checkAddressesRecoveredAreCorrect, - checkTotalAmountIsCorrect + checkTotalAmountIsCorrect, } from '../support/helpers/transfer-helpers'; import { checkErrorByTranslationId } from './common-steps'; import { daedalusMasterKeyButton, twelveWordOption } from '../pages/walletClaimTransferPage'; @@ -21,7 +18,12 @@ import { proceedRecoveryButton } from '../pages/restoreWalletPage'; import { errorMessage, errorPageTitle } from '../pages/errorPage'; import { amountField, feeField, totalAmountField } from '../pages/confirmTransactionPage'; import { walletAddComponent } from '../pages/basicSetupPage'; -import { backButton, formFieldOverridesClassicError, nextButton, transferButton } from '../pages/daedalusTransferPage'; +import { + backButton, + formFieldOverridesClassicError, + nextButton, + transferButton, +} from '../pages/daedalusTransferPage'; import { activeNavTab } from '../pages/walletPage'; Before({ tags: '@withWebSocketConnection' }, () => { @@ -40,7 +42,7 @@ After({ tags: '@withWebSocketConnection' }, () => { Given(/^My Daedalus wallet has funds/, () => { const daedalusAddresses = [ 'DdzFFzCqrhstBgE23pfNLvukYhpTPUKgZsXWLN5GsawqFZd4Fq3aVuGEHk11LhfMfmfBCFCBGrdZHVExjiB4FY5Jkjj1EYcqfTTNcczb', - 'DdzFFzCqrht74dr7DYmiyCobGFQcfLCsHJCCM6nEBTztrsEk5kwv48EWKVMFU9pswAkLX9CUs4yVhVxqZ7xCVDX1TdatFwX5W39cohvm' + 'DdzFFzCqrht74dr7DYmiyCobGFQcfLCsHJCCM6nEBTztrsEk5kwv48EWKVMFU9pswAkLX9CUs4yVhVxqZ7xCVDX1TdatFwX5W39cohvm', ]; mockRestoredDaedalusAddresses(daedalusAddresses); }); @@ -74,10 +76,7 @@ When(/^I click the back button$/, async function () { Then(/^I should see "This field is required." error message:$/, async function (data) { const error = data.hashes()[0]; - await checkErrorByTranslationId( - this, - formFieldOverridesClassicError, - error); + await checkErrorByTranslationId(this, formFieldOverridesClassicError, error); }); When(/^I confirm Daedalus transfer funds$/, async function () { @@ -89,27 +88,29 @@ Then(/^I should see the Create wallet screen$/, async function () { }); Then(/^I should see the Receive screen$/, async function () { - const receiveTitle = await i18n.formatMessage(this.driver, - { id: 'wallet.navigation.receive' }); + const receiveTitle = await i18n.formatMessage(this.driver, { id: 'wallet.navigation.receive' }); await this.waitUntilText(activeNavTab, receiveTitle); await this.driver.sleep(2000); }); Then(/^I should see an Error screen$/, async function () { - const errorPageTitleString = await i18n.formatMessage(this.driver, - { id: 'daedalusTransfer.errorPage.title.label' }); + const errorPageTitleString = await i18n.formatMessage(this.driver, { + id: 'daedalusTransfer.errorPage.title.label', + }); await this.waitUntilText(errorPageTitle, errorPageTitleString); }); Then(/^I should see 'Connection lost' error message$/, async function () { - const errorDescription = await i18n.formatMessage(this.driver, - { id: 'daedalusTransfer.error.webSocketRestoreError' }); + const errorDescription = await i18n.formatMessage(this.driver, { + id: 'daedalusTransfer.error.webSocketRestoreError', + }); await this.waitUntilText(errorMessage, errorDescription); }); Then(/^I should see 'Daedalus wallet without funds' error message$/, async function () { - const errorDescription = await i18n.formatMessage(this.driver, - { id: 'api.errors.noInputsError' }); + const errorDescription = await i18n.formatMessage(this.driver, { + id: 'api.errors.noInputsError', + }); await this.waitUntilText(errorMessage, errorDescription); }); @@ -127,4 +128,4 @@ When(/^I see transfer CONFIRM TRANSACTION Pop up:$/, async function (table) { await this.waitUntilContainsText(feeField, fields.fee); await this.waitUntilContainsText(amountField, fields.amount); await this.waitUntilContainsText(totalAmountField, totalRecoveredBalance); -}) +}); diff --git a/packages/yoroi-extension/features/step_definitions/general-settings-steps.js b/packages/yoroi-extension/features/step_definitions/general-settings-steps.js index f9507872fa..c7dfbbac94 100644 --- a/packages/yoroi-extension/features/step_definitions/general-settings-steps.js +++ b/packages/yoroi-extension/features/step_definitions/general-settings-steps.js @@ -5,15 +5,22 @@ import { camelCase } from 'lodash'; import { waitUntilUrlEquals, navigateTo } from '../support/helpers/route-helpers'; import i18n from '../support/helpers/i18n-helpers'; import { By, WebElement } from 'selenium-webdriver'; -import { complexitySelected, secondThemeSelected, settingsLayoutComponent, complexityLevelForm, languageSelector } from '../pages/settingsPage'; +import { + complexitySelected, + secondThemeSelected, + settingsLayoutComponent, + complexityLevelForm, + languageSelector, +} from '../pages/settingsPage'; export async function selectSubmenuSettings(customWorld: Object, buttonName: string) { const formattedButtonName = camelCase(buttonName); const buttonSelector = `.SubMenuItem_component.${formattedButtonName}`; await customWorld.click({ locator: buttonSelector, method: 'css' }); - await customWorld.waitForElement( - { locator: `.SubMenuItem_component.SubMenuItem_active.${formattedButtonName}`, method: 'css' } - ); + await customWorld.waitForElement({ + locator: `.SubMenuItem_component.SubMenuItem_active.${formattedButtonName}`, + method: 'css', + }); } export async function goToSettings(customWorld: Object) { @@ -47,7 +54,10 @@ When(/^I click on secondary menu "([^"]*)" item$/, async function (buttonName) { const formattedButtonName = camelCase(buttonName); const buttonSelector = `.SubMenuItem_component.${formattedButtonName}`; await this.click({ locator: buttonSelector, method: 'css' }); - await this.waitForElement({ locator: `.SubMenuItem_component.SubMenuItem_active.${formattedButtonName}`, method: 'css' }); + await this.waitForElement({ + locator: `.SubMenuItem_component.SubMenuItem_active.${formattedButtonName}`, + method: 'css', + }); }); When(/^I select second theme$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js index 96e66f830d..6f3afb5fd1 100644 --- a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js +++ b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js @@ -28,10 +28,11 @@ Then(/^I should not see the "Terms of use" screen anymore$/, async function () { }); Then(/^I should have "Terms of use" accepted$/, async function () { - const result = await this.driver.executeAsyncScript((done) => { - window.yoroi.stores.profile.getTermsOfUseAcceptanceRequest.execute() + const result = await this.driver.executeAsyncScript(done => { + window.yoroi.stores.profile.getTermsOfUseAcceptanceRequest + .execute() .then(done) - .catch((error) => done(error)); + .catch(error => done(error)); }); expect(result).to.equal(true); }); diff --git a/packages/yoroi-extension/features/step_definitions/memo-steps.js b/packages/yoroi-extension/features/step_definitions/memo-steps.js index 9327fdc819..11bfe636b3 100644 --- a/packages/yoroi-extension/features/step_definitions/memo-steps.js +++ b/packages/yoroi-extension/features/step_definitions/memo-steps.js @@ -33,10 +33,14 @@ Then(/^I delete the memo$/, async function () { await this.click({ locator: '.editMemoButton', method: 'css' }); await this.click(primaryButton); let memoComponent = await this.driver.findElement(By.css('.MemoDialogCommon_component')); - const deleteButton = await memoComponent.findElement(By.xpath('//button[@aria-label="delete memo"]')); + const deleteButton = await memoComponent.findElement( + By.xpath('//button[@aria-label="delete memo"]') + ); await deleteButton.click(); memoComponent = await this.driver.findElement(By.css('.MemoDialogCommon_component')); - const confirmDelete = await memoComponent.findElement(By.xpath('//button[contains(text(), "Delete")]')); + const confirmDelete = await memoComponent.findElement( + By.xpath('//button[contains(text(), "Delete")]') + ); await confirmDelete.click(); }); diff --git a/packages/yoroi-extension/features/step_definitions/migration-steps.js b/packages/yoroi-extension/features/step_definitions/migration-steps.js index 9191a70bc7..fbd1e1671a 100644 --- a/packages/yoroi-extension/features/step_definitions/migration-steps.js +++ b/packages/yoroi-extension/features/step_definitions/migration-steps.js @@ -25,7 +25,7 @@ async function getLastLaunchVersion(client: any) { async function setLastLaunchVersion(client: any, newVersion: string) { return await client.executeScript( - (ver) => yoroi.stores.profile.setLastLaunchVersion(ver), + ver => yoroi.stores.profile.setLastLaunchVersion(ver), newVersion ); } diff --git a/packages/yoroi-extension/features/step_definitions/select-language-steps.js b/packages/yoroi-extension/features/step_definitions/select-language-steps.js index 36f257800c..6be98a1e7c 100644 --- a/packages/yoroi-extension/features/step_definitions/select-language-steps.js +++ b/packages/yoroi-extension/features/step_definitions/select-language-steps.js @@ -32,10 +32,11 @@ Then(/^I should not see the language selection screen anymore$/, async function }); Then(/^I should have Japanese language set$/, async function () { - const result = await this.driver.executeAsyncScript((done) => { - window.yoroi.stores.profile.getProfileLocaleRequest.execute() + const result = await this.driver.executeAsyncScript(done => { + window.yoroi.stores.profile.getProfileLocaleRequest + .execute() .then(done) - .catch((error) => done(error)); + .catch(error => done(error)); }); expect(result).to.equal('ja-JP'); }); diff --git a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js index a9d2e0b907..915b43eb3f 100644 --- a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js +++ b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js @@ -5,7 +5,14 @@ import { By } from 'selenium-webdriver'; import chai from 'chai'; import moment from 'moment'; import i18n from '../support/helpers/i18n-helpers'; -import { failedTransactionElement, noTransactionsComponent, numberOfTransactions, pendingTransactionElement, showMoreButton, transactionListElement } from '../pages/walletTransactionsPage'; +import { + failedTransactionElement, + noTransactionsComponent, + numberOfTransactions, + pendingTransactionElement, + showMoreButton, + transactionListElement, +} from '../pages/walletTransactionsPage'; import { summaryTab } from '../pages/walletPage'; function verifyAllTxsFields( diff --git a/packages/yoroi-extension/features/step_definitions/uri-steps.js b/packages/yoroi-extension/features/step_definitions/uri-steps.js index b5cb6cdac6..15877937e3 100644 --- a/packages/yoroi-extension/features/step_definitions/uri-steps.js +++ b/packages/yoroi-extension/features/step_definitions/uri-steps.js @@ -3,9 +3,22 @@ import { When, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import { expect } from 'chai'; -import { truncateAddress, } from '../../app/utils/formatters'; +import { truncateAddress } from '../../app/utils/formatters'; import { amountInput } from '../pages/walletSendPage'; -import { copyToClipboardIcon, generateUriButton, generateUriIcon, invalidUriDialog, uriDetailsConfirmButton, uriDisplayDialog, uriGenerateDialog, uriLandingDialog, uriLandingDialogAcceptButton, uriVerifyDialog, uriVerifyDialogAddress, uriVerifyDialogAmount } from '../pages/walletReceivePage'; +import { + copyToClipboardIcon, + generateUriButton, + generateUriIcon, + invalidUriDialog, + uriDetailsConfirmButton, + uriDisplayDialog, + uriGenerateDialog, + uriLandingDialog, + uriLandingDialogAcceptButton, + uriVerifyDialog, + uriVerifyDialogAddress, + uriVerifyDialogAmount, +} from '../pages/walletReceivePage'; When(/^I click on "generate payment URL" button$/, async function () { await this.click(generateUriIcon); @@ -25,13 +38,17 @@ Then(/^I click on the copy to clipboard icon$/, async function () { await this.click(copyToClipboardIcon); }); -When(/^I open a cardano URI for address (([^"]*)) and ([0-9]+) ADA$/, async function (address, amount) { - // In practice, clicking a cardano URI will cause the browser to open a URL of this form - const uri = this.getExtensionUrl() + '#/send-from-uri?q=web+cardano:' + address + '?amount=' + amount; - await this.driver.get('about:blank'); // dummy step, but needed - await this.driver.get(uri); - await this.driver.sleep(500); -}); +When( + /^I open a cardano URI for address (([^"]*)) and ([0-9]+) ADA$/, + async function (address, amount) { + // In practice, clicking a cardano URI will cause the browser to open a URL of this form + const uri = + this.getExtensionUrl() + '#/send-from-uri?q=web+cardano:' + address + '?amount=' + amount; + await this.driver.get('about:blank'); // dummy step, but needed + await this.driver.get(uri); + await this.driver.sleep(500); + } +); Then(/^I should see and accept a warning dialog$/, async function () { await this.waitForElement(uriLandingDialog); @@ -51,16 +68,25 @@ When(/^I confirm the URI transaction details$/, async function () { Then(/^I should land on send wallet screen with prefilled parameters$/, async function (table) { const fields = table.hashes()[0]; - const rxInput = await this.driver.findElement(By.xpath("//input[@name='receiver']")).getAttribute('value'); + const rxInput = await this.driver + .findElement(By.xpath("//input[@name='receiver']")) + .getAttribute('value'); expect(rxInput).to.be.equal(fields.address); - const sendAmountInput = await this.driver.findElement(By.xpath("//input[@name='amount']")).getAttribute('value'); + const sendAmountInput = await this.driver + .findElement(By.xpath("//input[@name='amount']")) + .getAttribute('value'); expect(sendAmountInput).to.be.equal(fields.amount); }); When(/^I open an invalid cardano URI$/, async function () { const invalidAddress = 'Ae2tdPwUPEZKmw0y3AU3cXb5Chnasj6mvVNxV1H11997q3VW5IhbSfQwGpm'; const amount = '1'; - const uri = this.getExtensionUrl() + '#/send-from-uri?q=web+cardano:' + invalidAddress + '?amount=' + amount; + const uri = + this.getExtensionUrl() + + '#/send-from-uri?q=web+cardano:' + + invalidAddress + + '?amount=' + + amount; await this.driver.get('about:blank'); // dummy step, but needed await this.driver.get(uri); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js index 6383f02f07..f49ed295d8 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js @@ -1,7 +1,15 @@ // @flow import { Given, When, Then } from 'cucumber'; -import { delegationDashboardPage, delegationDashboardPageButton, delegationFormNextButton, delegationSuccessPage, delegationTxDialog, poolIdInput, stakePoolTicker } from '../pages/walletDelegationPage'; +import { + delegationDashboardPage, + delegationDashboardPageButton, + delegationFormNextButton, + delegationSuccessPage, + delegationTxDialog, + poolIdInput, + stakePoolTicker, +} from '../pages/walletDelegationPage'; import { delegationByIdTab } from '../pages/walletPage'; When(/^I go to the delegation by id screen$/, async function () { @@ -33,4 +41,3 @@ Given(/^I click on see dashboard$/, async function () { When(/^I should see the dashboard screen$/, async function () { await this.waitForElement(delegationDashboardPage); }); - diff --git a/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js b/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js index 66e210e54a..4c8537385a 100644 --- a/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js +++ b/packages/yoroi-extension/features/step_definitions/yoroi-transfer-steps.js @@ -36,16 +36,17 @@ import { } from '../pages/walletClaimTransferPage'; import { primaryButton } from '../pages/commonDialogPage'; import { fullScreenMessage } from '../pages/settingsPage'; +import { hardwareDisclaimerComponent, understandButton } from '../pages/walletDelegationPage'; async function confirmAttentionScreen(customWorld: Object) { // Attention screen - await customWorld.waitForElement({ locator: '.HardwareDisclaimer_component', method: 'css' }); + await customWorld.waitForElement(hardwareDisclaimerComponent); const disclaimerClassElement = await customWorld.driver.findElement( By.css('.HardwareDisclaimer_component') ); const checkbox = await disclaimerClassElement.findElement(By.xpath('//input[@type="checkbox"]')); await checkbox.click(); - await customWorld.click({ locator: '//button[text()="I understand"]', method: 'xpath' }); + await customWorld.click(understandButton); } Given(/^I am on the transfer start screen$/, async function () { From 2d15fb0b98e3992066167a074fc3d6d0d9d26daf Mon Sep 17 00:00:00 2001 From: Cristian Merlo Date: Tue, 30 Aug 2022 17:19:45 -0300 Subject: [PATCH 068/199] Fixed issue with lint --- .../features/step_definitions/trezor-steps.js | 11 ----------- .../step_definitions/wallet-delegation-steps.js | 4 ---- 2 files changed, 15 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/trezor-steps.js b/packages/yoroi-extension/features/step_definitions/trezor-steps.js index dff8bd6562..ec687af851 100644 --- a/packages/yoroi-extension/features/step_definitions/trezor-steps.js +++ b/packages/yoroi-extension/features/step_definitions/trezor-steps.js @@ -45,11 +45,7 @@ Then(/^I switch to Trezor-connect screen and allow using$/, async function () { }); Then(/^I press Yes on the Trezor emulator$/, async function () { -<<<<<<< HEAD for (let i = 1; i < 6; i++) { -======= - for (let i = 1; i < 4; i++) { ->>>>>>> 81dc2848d8d1b4eb27a469700ceb1622cbc6745c const pressYesResponse = await this.trezorController.emulatorPressYes(); expect(pressYesResponse.success, `${i} emulator-press-yes request is failed`).to.be.true; } @@ -75,7 +71,6 @@ Then(/^I start trezor emulator environment$/, async function () { const emulatorWipeResponse = await this.trezorController.emulatorWipe(); expect(emulatorWipeResponse.success, 'emulator-wipe request is failed').to.be.true; -<<<<<<< HEAD }); Then(/^I setup trezor emulator for ([^"]*)$/, async function (walletName) { @@ -83,12 +78,6 @@ Then(/^I setup trezor emulator for ([^"]*)$/, async function (walletName) { expect(restoreInfo).to.not.equal(undefined); const emulatorSetupResponse = await this.trezorController.emulatorSetup(restoreInfo.mnemonic); -======= - - const emulatorSetupResponse = await this.trezorController.emulatorSetup( - 'lyrics tray aunt muffin brisk ensure wedding cereal capital path replace weasel' - ); ->>>>>>> 81dc2848d8d1b4eb27a469700ceb1622cbc6745c expect(emulatorSetupResponse.success, 'emulator-setup request is failed').to.be.true; }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js index e23188ee6f..f49ed295d8 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js @@ -1,7 +1,6 @@ // @flow import { Given, When, Then } from 'cucumber'; -<<<<<<< HEAD import { delegationDashboardPage, delegationDashboardPageButton, @@ -11,9 +10,6 @@ import { poolIdInput, stakePoolTicker, } from '../pages/walletDelegationPage'; -======= -import { delegationDashboardPage, delegationDashboardPageButton, delegationFormNextButton, delegationSuccessPage, delegationTxDialog, poolIdInput, stakePoolTicker } from '../pages/walletDelegationPage'; ->>>>>>> 81dc2848d8d1b4eb27a469700ceb1622cbc6745c import { delegationByIdTab } from '../pages/walletPage'; When(/^I go to the delegation by id screen$/, async function () { From 52986c9ce6d68817ca232229ccb4b46015a56b62 Mon Sep 17 00:00:00 2001 From: Rafael Castro Date: Wed, 31 Aug 2022 09:55:11 -0300 Subject: [PATCH 069/199] fix tab character --- .../components/connect/ConnectPage.js | 29 +++++++++---------- .../components/connect/ConnectPage.scss | 1 + 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js index d8e98c05ce..dbe6337190 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js @@ -344,23 +344,22 @@ class ConnectPage extends Component { )} - { - hasWallets && !isAppAuth ? -

    -

    - {intl.formatMessage(messages.connectWalletNoHardwareSupported)} -

    + { hasWallets && !isAppAuth ? +
    +

    + {intl.formatMessage(messages.connectWalletNoHardwareSupported)} +

    -

    - {intl.formatMessage(messages.connectInfo)} -

    -

    - {intl.formatMessage(connectorMessages.messageReadOnly)} -

    +

    + {intl.formatMessage(messages.connectInfo)} +

    +

    + {intl.formatMessage(connectorMessages.messageReadOnly)} +

    -
    : null } +
    : null }
    ); } diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss index fecb2f3ecb..0aad57db62 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss @@ -122,3 +122,4 @@ } } } + From 0f03f477f599bb1ed4298698ac38a8863448ef9a Mon Sep 17 00:00:00 2001 From: Rafael Castro Date: Wed, 31 Aug 2022 10:10:35 -0300 Subject: [PATCH 070/199] fix indentation --- .../app/ergo-connector/components/connect/ConnectPage.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss index 0aad57db62..60cfbd6de9 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss @@ -5,7 +5,7 @@ overflow: auto; &.isNightly { height: calc(100vh - 138px); - } + } } .connectWrapper { padding-top: 47px; @@ -91,7 +91,7 @@ } .bottom { - border-top: 1px solid #dce0e9; + border-top: 1px solid #dce0e9; padding: 15px 32px; .infoText { From 9a5cbf7b398644f1eb15a4145353fb55ab0429a8 Mon Sep 17 00:00:00 2001 From: Rafael Castro Date: Wed, 31 Aug 2022 10:13:47 -0300 Subject: [PATCH 071/199] fix indentation and tab --- .../components/connect/ConnectPage.scss | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss index 60cfbd6de9..4d827ca966 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss @@ -1,8 +1,8 @@ .component { - display: flex; - flex-direction: column; - height: calc(100vh - 52px); - overflow: auto; + display: flex; + flex-direction: column; + height: calc(100vh - 52px); + overflow: auto; &.isNightly { height: calc(100vh - 138px); } @@ -36,10 +36,10 @@ .titleWallet { - display: flex; - & * + * { - padding-left: 10px; - } + display: flex; + & * + * { + padding-left: 10px; + } } .noWallets { From 12172977a7f8f53b76ae7c6b888b170277161c14 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 14 Sep 2022 13:14:56 +0300 Subject: [PATCH 072/199] little fixes --- .../features/step_definitions/common-steps.js | 3 +-- packages/yoroi-extension/features/support/webdriver.js | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 3da1964e7d..9d2ec939e3 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -79,9 +79,8 @@ import { walletRecoveryPhraseDisplayDialog } from '../pages/createWalletPage'; import * as helpers from '../support/helpers/helpers'; -import { extensionTabName } from '../support/windowManager'; +import { extensionTabName, WindowManager } from '../support/windowManager'; import { MockDAppWebpage } from '../mock-dApp-webpage'; -import { WindowManager } from '../support/windowManager'; const simpleNodeLogger = require('simple-node-logger'); diff --git a/packages/yoroi-extension/features/support/webdriver.js b/packages/yoroi-extension/features/support/webdriver.js index 0a10e6fb2b..b461d1e85f 100644 --- a/packages/yoroi-extension/features/support/webdriver.js +++ b/packages/yoroi-extension/features/support/webdriver.js @@ -127,13 +127,18 @@ function CustomWorld(cmdInput: WorldInput) { } } this.driver = builder.build(); - this.getBrowser = (): string => cmdInput.parameters.browser; this._allLoggers = []; this.trezorController = undefined; + this.windowManager = undefined; + + this.webDriverLogger = undefined; + + this.trezorEmuLogger = undefined; + this.addToLoggers = (logger: any) => { this._allLoggers.push(logger); } ; From 7f3a90c186cfe0bc49b520ef2419fe5c97ffb727 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 14 Sep 2022 15:39:54 +0300 Subject: [PATCH 073/199] little fixes --- packages/yoroi-extension/features/dashboard.feature | 2 +- packages/yoroi-extension/features/settings-ui.feature | 6 +++--- .../features/step_definitions/wallet-delegation-steps.js | 1 - packages/yoroi-extension/features/trezor-emulator.feature | 2 +- packages/yoroi-extension/features/yoroi-transfer.feature | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/yoroi-extension/features/dashboard.feature b/packages/yoroi-extension/features/dashboard.feature index c82d6eacfb..ec7a0d74f8 100644 --- a/packages/yoroi-extension/features/dashboard.feature +++ b/packages/yoroi-extension/features/dashboard.feature @@ -24,7 +24,7 @@ Feature: Yoroi delegation dashboard | asdfasdfasdf | Given The expected transaction is "hKYAgYJYIDZ36Gx7ppmv3BzVfULyRvhvaa79dgJQBqx4MT+tK7ohAAGBglg5AfiMMmOcqBWRIjRN6CEig4T8YKJcOWtIDaUVnSFW21zUiJyEEQ1N6QwNUDtRuETbPm/YeZEjiZW7GgCV8hsCGgACpGUDGhH+lM0EgYIBggBYHFbbXNSInIQRDU3pDA1QO1G4RNs+b9h5kSOJlbsFoVgd4VbbXNSInIQRDU3pDA1QO1G4RNs+b9h5kSOJlbsaAExLQKEAgoJYIDHFsozgC4AMMNymh4uSd8Xls6VSRnf9Dxv6kiJPzsubWEAXpQuoGfhAzvgfp0H9ouqVNr4ZQPpQnFG9frwUkkyzA7dLIl1GmIuFbkJFMp3AakfKpXSZ9s+3dpaw9hYFkKgLglgg6cWNnhkPKitPspqy3T6+Lqi2VU1F/s8JE36FUprlBHBYQICDQmLn20i7qEzQSnFGhJv3Yp2qiAFF/6XxaqOeIvva6u/jxDYC/CFoA3UV4B6thf4QFJZ9owY9EsOhQuu14A319g==" When I confirm Yoroi transfer funds - Then I should see the dashboard stake1u9tdkhx53zwggygdfh5scr2s8dgms3xm8ehas7v3ywyetwcufngyf + Then I should see the dashboard screen @it-157 Scenario: User can withdraw Ledger rewards from the dashboard w/ deregister (IT-157) Given I connected Ledger device 707fa118bf6b84 diff --git a/packages/yoroi-extension/features/settings-ui.feature b/packages/yoroi-extension/features/settings-ui.feature index e11787c02a..a73c12bce6 100644 --- a/packages/yoroi-extension/features/settings-ui.feature +++ b/packages/yoroi-extension/features/settings-ui.feature @@ -93,9 +93,9 @@ Feature: Wallet UI Settings Examples: | walletName | | | first Edited | 2 words name | - |ウォレットの追加 | Japanese | - |지갑 추가 | Korean | - |НАСТРОЙКИ | Russian | + | ウォレットの追加 | Japanese | + | 지갑 추가 | Korean | + | НАСТРОЙКИ | Russian | | a | 1-characters length | | asdfghjklpoiuytrewqazxcvbnmlkjhgfdsaqwer | 40 characters length | diff --git a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js index bbe002f154..62a6fc5722 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-delegation-steps.js @@ -72,4 +72,3 @@ Given(/^I click on see dashboard$/, async function () { When(/^I should see the dashboard screen$/, async function () { await this.waitForElement({ locator: '.StakingDashboard_page', method: 'css' }); }); - diff --git a/packages/yoroi-extension/features/trezor-emulator.feature b/packages/yoroi-extension/features/trezor-emulator.feature index 8426ca6fdc..dd88f61045 100644 --- a/packages/yoroi-extension/features/trezor-emulator.feature +++ b/packages/yoroi-extension/features/trezor-emulator.feature @@ -90,7 +90,7 @@ Feature: Trezor wallet emulator @Trezor-006 Scenario: Trezor (emulator). User can transfer funds from a trezor wallet - Given I switch to the advanced level + Given I switched to the advanced level And I am on the transfer start screen When I click on the byron button on the transfer screen When I click on the icarus tab diff --git a/packages/yoroi-extension/features/yoroi-transfer.feature b/packages/yoroi-extension/features/yoroi-transfer.feature index d734f439f8..aaa8af8dd2 100644 --- a/packages/yoroi-extension/features/yoroi-transfer.feature +++ b/packages/yoroi-extension/features/yoroi-transfer.feature @@ -3,7 +3,7 @@ Feature: Transfer Yoroi Wallet funds Background: Given I have opened the extension And I have completed the basic setup - And I switch to the advanced level + And I switched to the advanced level And I navigate back to the main page Then I should see the Create wallet screen From f9f4accd74672048f6ffa4727138cc6e951f3870 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 14 Sep 2022 18:57:17 +0300 Subject: [PATCH 074/199] created time constants --- .../features/step_definitions/common-steps.js | 20 ++++++++------ .../step_definitions/transactions-steps.js | 7 ++--- .../support/helpers/common-constants.js | 10 ++++++- .../features/support/webdriver.js | 26 ++++++++++++------- 4 files changed, 42 insertions(+), 21 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 9d2ec939e3..fabb026194 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -21,6 +21,10 @@ import { testRunsDataDir, snapshotsDir, commonWalletPassword, + fiveMinute, + oneSecond, + halfSecond, + quarterMinute, halfMinute, } from '../support/helpers/common-constants'; import { expect } from 'chai'; import { satisfies } from 'semver'; @@ -95,7 +99,7 @@ const testProgress = { }; BeforeAll(() => { - setDefaultTimeout(20 * 1000); + setDefaultTimeout(halfMinute); CardanoServer.getMockServer({}); ErgoServer.getMockServer({}); @@ -199,7 +203,7 @@ After({ tags: '@trezorEmulatorTest' }, async function () { }); Before({ tags: '@smoke' }, () => { - setDefaultTimeout(5 * 60 * 1000); + setDefaultTimeout(fiveMinute); }); After(async function (scenario) { @@ -214,7 +218,7 @@ After(async function (scenario) { } await this.windowManager.switchTo(extensionTabName); await this.driver.quit(); - await helpers.sleep(500); + await helpers.sleep(halfSecond); }); export async function getPlates(customWorld: any): Promise { @@ -361,7 +365,7 @@ export async function checkErrorByTranslationId( errorSelector: LocatorObject, errorObject: Object ) { - await client.waitUntilText(errorSelector, await client.intl(errorObject.message), 15000); + await client.waitUntilText(errorSelector, await client.intl(errorObject.message), quarterMinute); } Then(/^I pause the test to debug$/, async function () { @@ -494,7 +498,7 @@ Given(/^I refresh the page$/, async function () { this.webDriverLogger.info(`Step: I refresh the page`); await this.driver.navigate().refresh(); // wait for page to refresh - await this.driver.sleep(500); + await this.driver.sleep(halfSecond); await this.waitForElement({ locator: '.YoroiClassic', method: 'css' }); }); @@ -503,7 +507,7 @@ Given(/^I restart the browser$/, async function () { await this.driver.manage().deleteAllCookies(); await this.driver.navigate().refresh(); // wait for page to refresh - await this.driver.sleep(500); + await this.driver.sleep(halfSecond); await this.waitForElement({ locator: '.YoroiClassic', method: 'css' }); }); @@ -530,7 +534,7 @@ Given(/^I import a snapshot named ([^"]*)$/, async function (snapshotName) { // refresh page to trigger migration await this.driver.navigate().refresh(); // wait for page to refresh - await this.driver.sleep(1500); + await this.driver.sleep(oneSecond + halfSecond); await this.waitForElement({ locator: '.YoroiClassic', method: 'css' }); }); @@ -751,5 +755,5 @@ Then(/^Debug. Take screenshot$/, async function () { }); Then(/^Debug. Make driver sleep for 2 seconds$/, async function () { - await this.driver.sleep(2000); + await this.driver.sleep(2 * oneSecond); }); \ No newline at end of file diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index 4932b91247..907f3de0e1 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -18,12 +18,13 @@ import { selectSendingAmountDropDown, sendAllItem } from '../pages/walletSendPage'; +import { halfSecond, oneMinute } from '../support/helpers/common-constants'; Given(/^I have a wallet with funds$/, async function () { await this.waitUntilContainsText( { locator: '.NavWalletDetails_amount', method: 'css' }, 'ADA', - 60 * 1000 + oneMinute ); const balanceTextElement = await this.findElement({ locator: '.NavWalletDetails_amount', method: 'css' }); const balanceText = await balanceTextElement.getText(); @@ -121,7 +122,7 @@ When(/^I click on the next button in the wallet send form$/, async function () { * * I attempt to fix it by just clicking twice after a delay */ - await this.driver.sleep(500); + await this.driver.sleep(halfSecond); try { await this.click({ locator: button, method: 'css' }); } catch (e) { @@ -240,6 +241,6 @@ When(/^I select token "([^"]*)"$/, async function (tokenName) { When(/^I open the amount dropdown and select send all$/, async function () { await this.click(selectSendingAmountDropDown); - await this.driver.sleep(500); + await this.driver.sleep(halfSecond); await this.click(sendAllItem); }); diff --git a/packages/yoroi-extension/features/support/helpers/common-constants.js b/packages/yoroi-extension/features/support/helpers/common-constants.js index 116bea2331..a9146da1aa 100644 --- a/packages/yoroi-extension/features/support/helpers/common-constants.js +++ b/packages/yoroi-extension/features/support/helpers/common-constants.js @@ -12,4 +12,12 @@ export const emailOptions = { }; export const commonWalletPassword = 'asdfasdfasdf'; -export const txSuccessfulStatuses = ['high', 'medium', 'low']; \ No newline at end of file +export const txSuccessfulStatuses = ['high', 'medium', 'low']; +export const halfSecond = 500; +export const oneSecond = 1000; +export const defaultRepeatPeriod = oneSecond; +export const defaultWaitTimeout = 10 * oneSecond; +export const quarterMinute = 15 * oneSecond; +export const halfMinute = 30 * oneSecond; +export const oneMinute = 60 * oneSecond; +export const fiveMinute = 5 * oneMinute; diff --git a/packages/yoroi-extension/features/support/webdriver.js b/packages/yoroi-extension/features/support/webdriver.js index b461d1e85f..628eb221e3 100644 --- a/packages/yoroi-extension/features/support/webdriver.js +++ b/packages/yoroi-extension/features/support/webdriver.js @@ -1,6 +1,6 @@ // @flow -import { setWorldConstructor, setDefaultTimeout } from 'cucumber'; +import { setWorldConstructor } from 'cucumber'; import { Builder, Key, until, error, promise, WebElement } from 'selenium-webdriver'; import chrome from 'selenium-webdriver/chrome'; import firefox from 'selenium-webdriver/firefox'; @@ -9,6 +9,13 @@ import { RustModule } from '../../app/api/ada/lib/cardanoCrypto/rustLoader'; import { getMethod } from './helpers/helpers'; import { WebDriverError } from 'selenium-webdriver/lib/error'; import * as helpers from './helpers/helpers'; +import { + defaultWaitTimeout, + defaultRepeatPeriod, + halfSecond, + quarterMinute, + oneMinute, +} from './helpers/common-constants'; const fs = require('fs'); @@ -29,12 +36,10 @@ function encode(file) { * We then note the mapping of the extension ID to the random UUID is stored in about:config * Under the key "extensions.webextensions.uuids". * Therefore, we specify a fixed extension ID for Yoroi in the manifest - * Then we use Selenium to override the config to manually specify a a fixed UUID + * Then we use Selenium to override the config to manually specify a fixed UUID */ const firefoxExtensionId = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'; const firefoxUuidMapping = `{"{530f7c6c-6077-4703-8f71-cb368c663e35}":"${firefoxExtensionId}"}`; -const defaultWaitTimeout = 10 * 1000; -const defaultRepeatPeriod = 1000; function getBraveBuilder() { return new Builder().forBrowser('chrome').setChromeOptions( @@ -167,8 +172,8 @@ function CustomWorld(cmdInput: WorldInput) { await this.driver.get(url); } catch (e) { if (e instanceof WebDriverError) { - this.webDriverLogger.info(`Webdriver: Caught the WebDriverError. Sleep for 1 second and retry`); - await helpers.sleep(500); + this.webDriverLogger.info(`Webdriver: Caught the WebDriverError. Sleep for 0.5 second and retry`); + await helpers.sleep(halfSecond); continue; } } @@ -233,7 +238,11 @@ function CustomWorld(cmdInput: WorldInput) { return this.driver.wait(condition); }; - this.waitUntilText = async (locator: LocatorObject, text, timeout = 75000) => { + this.waitUntilText = async ( + locator: LocatorObject, + text, + timeout = oneMinute + quarterMinute + ) => { this.webDriverLogger.info(`Webdriver: Waiting Until "${JSON.stringify(locator)}" contains "${text}"`); await this.driver.wait(async () => { try { @@ -245,7 +254,7 @@ function CustomWorld(cmdInput: WorldInput) { }, timeout); }; - this.waitUntilContainsText = async (locator: LocatorObject, text, timeout = 15000) => { + this.waitUntilContainsText = async (locator: LocatorObject, text, timeout = quarterMinute) => { this.webDriverLogger.info(`Webdriver: Waiting for "${JSON.stringify(locator)}" to contain text "${text}"`); await this.driver.wait(async () => { try { @@ -401,7 +410,6 @@ function CustomWorld(cmdInput: WorldInput) { RustModule.load() .then(() => { setWorldConstructor(CustomWorld); - setDefaultTimeout(30 * 1000); return undefined; }) .catch(); From ea00a2a694046925f2e0ac8e94c5c1886377dfbd Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 16 Sep 2022 12:03:46 +0300 Subject: [PATCH 075/199] Updated addresses-generation-steps.js and walletReceivePage.js --- .../features/pages/walletReceivePage.js | 49 ++++++++++++++- .../addresses-generation-steps.js | 62 +++++++------------ 2 files changed, 71 insertions(+), 40 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletReceivePage.js b/packages/yoroi-extension/features/pages/walletReceivePage.js index 060259f236..5764614af2 100644 --- a/packages/yoroi-extension/features/pages/walletReceivePage.js +++ b/packages/yoroi-extension/features/pages/walletReceivePage.js @@ -1,21 +1,64 @@ // @flow import type { LocatorObject } from '../support/webdriver'; +import i18n from '../support/helpers/i18n-helpers'; +import { By } from 'selenium-webdriver'; -export const getGeneratedAddressLocator = (rowIndex: number): LocatorObject => { +const getReceiveSubTabButton = (translatedText: string) => { + return { locator: `//span[contains(text(), "${translatedText}")]`, method: 'xpath' }; +} + +export const getGeneratedAddress = (rowIndex: number): LocatorObject => { return { locator: `.generatedAddress-${rowIndex + 1} .RawHash_hash`, method: 'css', }; }; -export const getAddressLocator = (address: string): LocatorObject => { +export const getAddress = (address: string): LocatorObject => { return { locator: `//div[contains(text(), "${address}")]`, method: 'xpath', }; }; +export const getSubTabButton = (chain: string, kind: string): LocatorObject => { + return { locator: `div.${chain}.${kind}.ReceiveNavButton_wrapper`, method: 'css' }; +}; + +export const getAllAddressesButton = async (): Promise => { + const translatedText = await i18n.formatMessage(this.driver, { + id: 'wallet.receive.navigation.allLabel', + }); + return getReceiveSubTabButton(translatedText); +}; + +export const getUnusedAddressesButton = async (): Promise => { + const translatedText = await i18n.formatMessage(this.driver, { + id: 'wallet.receive.navigation.unusedLabel', + }); + return getReceiveSubTabButton(translatedText); +}; + +export const getUsedAddressesButton = async (): Promise => { + const translatedText = await i18n.formatMessage(this.driver, { + id: 'wallet.receive.navigation.usedLabel', + }); + return getReceiveSubTabButton(translatedText); +}; + +export const getHasBalanceButton = async (): Promise => { + const translatedText = await i18n.formatMessage(this.driver, { + id: 'wallet.receive.navigation.hasBalanceLabel', + }); + return getReceiveSubTabButton(translatedText); +}; + +export const getAddressFromAddressRow = async (row: any): Promise => { + const topAddrElem = await row.findElement(By.css(walletAddressLocator)); + return await topAddrElem.getText(); +}; + export const addressErrorPhrase: LocatorObject = { locator: '.StandardHeader_error', method: 'css', @@ -35,6 +78,8 @@ export const verifyAddressButton: LocatorObject = { method: 'css', }; +export const walletAddressRow: LocatorObject = { locator: 'WalletReceive_walletAddress', method: 'css' }; +export const walletAddressLocator = '.WalletReceive_addressHash'; export const verifyAddressHWButton: LocatorObject = { locator: '.VerifyAddressDialog_component .primary', method: 'css', diff --git a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js index d0e830cf71..5b3fe5eef7 100644 --- a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js @@ -1,7 +1,6 @@ // @flow import { Given, When, Then } from 'cucumber'; -import { By } from 'selenium-webdriver'; import i18n from '../support/helpers/i18n-helpers'; import { expect } from 'chai'; import { checkIfElementsInArrayAreUnique } from '../support/helpers/helpers'; @@ -10,11 +9,18 @@ import { receiveTab } from '../pages/walletPage'; import { addressErrorPhrase, generateAddressButton, - getAddressLocator, - getGeneratedAddressLocator, + getAddress, + getGeneratedAddress, + getSubTabButton, addressBookTab, rewardAddressTab, yourWalletAddrHeader, + getAllAddressesButton, + getUnusedAddressesButton, + getUsedAddressesButton, + getHasBalanceButton, + walletAddressRow, + getAddressFromAddressRow, } from '../pages/walletReceivePage'; Given(/^Revamp. I go to the receive screen$/, async function () { @@ -30,7 +36,8 @@ When(/^I click on the Generate new address button$/, async function () { }); When(/^I click on the ([^ ]*) ([^ ]*) tab$/, async function (kind, chain) { - await this.click({ locator: `div.${chain}.${kind}.ReceiveNavButton_wrapper`, method: 'css' }); + const subTabButtonLocator = getSubTabButton(chain, kind); + await this.click(subTabButtonLocator); }); When(/^I click on the top-level address book tab$/, async function () { @@ -42,37 +49,22 @@ When(/^I click on the top-level reward address tab$/, async function () { }); When(/^I click on the All addresses button$/, async function () { - const hideUsedText = await i18n.formatMessage(this.driver, { - id: 'wallet.receive.navigation.allLabel', - }); - await this.click({ locator: `//span[contains(text(), "${hideUsedText}")]`, method: 'xpath' }); + await this.click(await getAllAddressesButton()); }); When(/^I click on the Unused addresses button$/, async function () { - const hideUsedText = await i18n.formatMessage(this.driver, { - id: 'wallet.receive.navigation.unusedLabel', - }); - await this.click({ locator: `//span[contains(text(), "${hideUsedText}")]`, method: 'xpath' }); + await this.click(await getUnusedAddressesButton()); }); When(/^I click on the Used addresses button$/, async function () { - const hideUsedText = await i18n.formatMessage(this.driver, { - id: 'wallet.receive.navigation.usedLabel', - }); - await this.click({ locator: `//span[contains(text(), "${hideUsedText}")]`, method: 'xpath' }); + await this.click(await getUsedAddressesButton()); }); When(/^I click on the HasBalance addresses button$/, async function () { - const hideUsedText = await i18n.formatMessage(this.driver, { - id: 'wallet.receive.navigation.hasBalanceLabel', - }); - await this.click({ locator: `//span[contains(text(), "${hideUsedText}")]`, method: 'xpath' }); + await this.click(await getHasBalanceButton()); }); When(/^I click on the Generate new address button ([0-9]+) times$/, async function (times) { for (let curr = 1; curr <= times; curr++) { await this.click(generateAddressButton); - await this.waitForElement({ - locator: `.generatedAddress-${curr + 1} .RawHash_hash`, - method: 'css', - }); + await this.waitForElement(getGeneratedAddress(curr)); } }); @@ -81,29 +73,23 @@ Then(/^I should see my latest address "([^"]*)" at the top$/, async function (ad }); Then(/^I should see at least ([^"]*) addresses$/, async function (numAddresses) { - const rows = await this.driver.findElements(By.css('.WalletReceive_walletAddress')); + const rows = await this.findElements(walletAddressRow); expect(rows.length).be.at.least(Number.parseInt(numAddresses, 10)); }); Then( /^I should see ([^"]*) addresses with address "([^"]*)" at the top$/, async function (numAddresses, address) { - const rows = await this.driver.findElements(By.css('.WalletReceive_walletAddress')); + const rows = await this.findElements(walletAddressRow); expect(rows.length).to.equal(Number.parseInt(numAddresses, 10)); - - const topAddrElem = await rows[0].findElement(By.css('.WalletReceive_addressHash')); - const topAddr = await topAddrElem.getText(); + const topAddr = await getAddressFromAddressRow(rows[0]); expect(topAddr).to.equal(truncateAddressShort(address)); } ); Then(/^I see every generated address is unique$/, async function () { - const addresses = await this.driver.findElements(By.xpath("//div[@class='RawHash_hash']")); - - const addressesStringArray = Array.from({ length: addresses.length }, (x, i) => i + 1).map( - async i => - await this.driver.findElement(By.css(`.generatedAddress-${i} .RawHash_hash`)).getText() - ); + const addresses = await this.findElements(walletAddressRow); + const addressesStringArray = addresses.map(async (row) => await getAddressFromAddressRow(row)); await Promise.all(addressesStringArray).then(async completed => { const unique = checkIfElementsInArrayAreUnique.call(this, completed); @@ -115,17 +101,17 @@ Then(/^I see every generated address is unique$/, async function () { Then(/^I should see the addresses exactly list them$/, async function (table) { const rows = table.hashes(); const waitUntilAddressesAppeared = rows.map((row, index) => - this.waitUntilText(getGeneratedAddressLocator(index), truncateAddressShort(row.address)) + this.waitUntilText(getGeneratedAddress(index), truncateAddressShort(row.address)) ); const noMoreAddressAppeared = this.waitForElementNotPresent( - getGeneratedAddressLocator(rows.length + 1) + getGeneratedAddress(rows.length + 1) ); waitUntilAddressesAppeared.push(noMoreAddressAppeared); await Promise.all(waitUntilAddressesAppeared); }); Then(/^I shouldn't see the address "([^"]*)"$/, async function (address) { - await this.waitForElementNotPresent(getAddressLocator(truncateAddressShort(address))); + await this.waitForElementNotPresent(getAddress(truncateAddressShort(address))); }); Then(/I should see an error about max unused addresses/, async function () { From c1b2c4e0756f7de94c875b59180ac6d03b1acf6f Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 16 Sep 2022 13:05:21 +0300 Subject: [PATCH 076/199] Updated basicSetupPage.js, common-steps.js, general-settings-steps.js, settingsPage.js --- .../features/pages/basicSetupPage.js | 6 +++ .../features/pages/settingsPage.js | 38 +++++++++++++++++ .../features/step_definitions/common-steps.js | 20 ++++----- .../general-settings-steps.js | 41 ++----------------- 4 files changed, 57 insertions(+), 48 deletions(-) diff --git a/packages/yoroi-extension/features/pages/basicSetupPage.js b/packages/yoroi-extension/features/pages/basicSetupPage.js index 381f53a890..54e46885fb 100644 --- a/packages/yoroi-extension/features/pages/basicSetupPage.js +++ b/packages/yoroi-extension/features/pages/basicSetupPage.js @@ -1,6 +1,7 @@ // @flow import type { LocatorObject } from '../support/webdriver'; +import { By } from 'selenium-webdriver'; // language select page export const languageSelectionForm: LocatorObject = { @@ -21,5 +22,10 @@ export const termsOfUseComponent: LocatorObject = { locator: '.TermsOfUseForm_component', method: 'css', }; +export const getTosCheckbox = async (customWorld: Object): Promise => { + const tosClassElement = await customWorld.findElement(termsOfUseComponent); + return await tosClassElement.findElement(By.xpath('//input[@type="checkbox"]')); +}; + // uri prompt page export const walletAddComponent: LocatorObject = { locator: '.WalletAdd_component', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/settingsPage.js b/packages/yoroi-extension/features/pages/settingsPage.js index f4d4051fd2..1deecc41d6 100644 --- a/packages/yoroi-extension/features/pages/settingsPage.js +++ b/packages/yoroi-extension/features/pages/settingsPage.js @@ -1,6 +1,42 @@ // @flow import type { LocatorObject } from '../support/webdriver'; +import { camelCase } from 'lodash'; +import { navigateTo, waitUntilUrlEquals } from '../support/helpers/route-helpers'; +import { By, WebElement } from 'selenium-webdriver'; + +export async function selectSubmenuSettings(customWorld: Object, buttonName: string) { + const formattedButtonName = camelCase(buttonName); + const buttonSelector = `.SubMenuItem_component.${formattedButtonName}`; + await customWorld.click({ locator: buttonSelector, method: 'css' }); + await customWorld.waitForElement({ + locator: `.SubMenuItem_component.SubMenuItem_active.${formattedButtonName}`, + method: 'css', + }); +} + +export async function goToSettings(customWorld: Object) { + await navigateTo.call(customWorld, '/settings'); + await navigateTo.call(customWorld, '/settings/general'); + + await waitUntilUrlEquals.call(customWorld, '/settings/general'); + await customWorld.waitForElement(settingsLayoutComponent); +} + +export async function getComplexityLevelButton( + customWorld: Object, + isLow: boolean = true +): Promise { + await customWorld.waitForElement(complexityLevelForm); + const levels = await customWorld.driver.findElements(By.css('.ComplexityLevelForm_card')); + let card; + if (isLow) { + card = levels[0]; + } else { + card = levels[levels.length - 1]; + } + return await card.findElement(By.xpath('.//button')); +} export const fullScreenMessage: LocatorObject = { locator: '.FullscreenMessage_title', @@ -52,6 +88,8 @@ export const secondThemeSelected: LocatorObject = { method: 'css', }; +export const revampThemeButton: LocatorObject = { locator: 'switchToRevampButton', method: 'id' }; + // Change password dialog export const currentPasswordInput: LocatorObject = { diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 229d8e8e26..f149c18510 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -26,11 +26,6 @@ import stableStringify from 'json-stable-stringify'; import type { RestorationInput } from '../mock-chain/TestWallets'; import { waitUntilUrlEquals, navigateTo } from '../support/helpers/route-helpers'; import { promises as fsAsync } from 'fs'; -import { - selectSubmenuSettings, - getComplexityLevelButton, - goToSettings, -} from './general-settings-steps'; import type { LocatorObject } from '../support/webdriver'; import { walletButton } from '../pages/sidebarPage'; import { getWalletButtonByPlate } from '../pages/walletsListPage'; @@ -67,11 +62,18 @@ import { import { walletNameText } from '../pages/walletPage'; import { continueButton, + getTosCheckbox, languageSelectionForm, termsOfUseComponent, walletAddComponent, } from '../pages/basicSetupPage'; -import { settingsLayoutComponent } from '../pages/settingsPage'; +import { + getComplexityLevelButton, + goToSettings, + revampThemeButton, + selectSubmenuSettings, + settingsLayoutComponent, +} from '../pages/settingsPage'; import { allowButton, finishButton, @@ -375,8 +377,7 @@ Given(/^I have completed the basic setup$/, async function () { await this.click(continueButton); // ToS page await this.waitForElement(termsOfUseComponent); - const tosClassElement = await this.driver.findElement(By.css('.TermsOfUseForm_component')); - const checkbox = await tosClassElement.findElement(By.xpath('//input[@type="checkbox"]')); + const checkbox = await getTosCheckbox(); await checkbox.click(); await this.click(continueButton); // uri prompt page @@ -668,8 +669,7 @@ Then(/^Revamp. I switch to revamp version$/, async function () { this.webDriverLogger.info(`Step: Revamp. I switch to revamp version`); await goToSettings(this); await selectSubmenuSettings(this, 'general'); - const revampButton = await this.driver.findElement(By.id('switchToRevampButton')); - await revampButton.click(); + await this.click(revampThemeButton); }); Then(/^Revamp. I go to the wallet ([^"]*)$/, async function (walletName) { diff --git a/packages/yoroi-extension/features/step_definitions/general-settings-steps.js b/packages/yoroi-extension/features/step_definitions/general-settings-steps.js index c7dfbbac94..42661532ca 100644 --- a/packages/yoroi-extension/features/step_definitions/general-settings-steps.js +++ b/packages/yoroi-extension/features/step_definitions/general-settings-steps.js @@ -2,50 +2,15 @@ import { When, Then } from 'cucumber'; import { camelCase } from 'lodash'; -import { waitUntilUrlEquals, navigateTo } from '../support/helpers/route-helpers'; import i18n from '../support/helpers/i18n-helpers'; -import { By, WebElement } from 'selenium-webdriver'; import { complexitySelected, - secondThemeSelected, - settingsLayoutComponent, - complexityLevelForm, + getComplexityLevelButton, + goToSettings, languageSelector, + secondThemeSelected, } from '../pages/settingsPage'; -export async function selectSubmenuSettings(customWorld: Object, buttonName: string) { - const formattedButtonName = camelCase(buttonName); - const buttonSelector = `.SubMenuItem_component.${formattedButtonName}`; - await customWorld.click({ locator: buttonSelector, method: 'css' }); - await customWorld.waitForElement({ - locator: `.SubMenuItem_component.SubMenuItem_active.${formattedButtonName}`, - method: 'css', - }); -} - -export async function goToSettings(customWorld: Object) { - await navigateTo.call(customWorld, '/settings'); - await navigateTo.call(customWorld, '/settings/general'); - - await waitUntilUrlEquals.call(customWorld, '/settings/general'); - await customWorld.waitForElement(settingsLayoutComponent); -} - -export async function getComplexityLevelButton( - customWorld: Object, - isLow: boolean = true -): Promise { - await customWorld.waitForElement(complexityLevelForm); - const levels = await customWorld.driver.findElements(By.css('.ComplexityLevelForm_card')); - let card; - if (isLow) { - card = levels[0]; - } else { - card = levels[levels.length - 1]; - } - return await card.findElement(By.xpath('.//button')); -} - When(/^I navigate to the general settings screen$/, async function () { await goToSettings(this); }); From a271e2499b33e4dbbc63bcdc6f2c8f08172957d4 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 16 Sep 2022 14:22:43 +0300 Subject: [PATCH 077/199] Updated installation-procedure-steps.js, main-ui-steps.js, walletPage.js, walletTransactionsPage.js --- .../features/pages/walletPage.js | 21 ++++++++++++++ .../features/pages/walletTransactionsPage.js | 10 +++++++ .../installation-procedure-steps.js | 12 ++------ .../step_definitions/main-ui-steps.js | 28 ++++++------------- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletPage.js b/packages/yoroi-extension/features/pages/walletPage.js index 058d8b140c..3a35427511 100644 --- a/packages/yoroi-extension/features/pages/walletPage.js +++ b/packages/yoroi-extension/features/pages/walletPage.js @@ -2,6 +2,7 @@ // Revamped wallets list elements import type { LocatorObject } from '../support/webdriver'; +import { By } from 'selenium-webdriver'; export const summaryTab: LocatorObject = { locator: 'summary', method: 'css' }; export const sendTab: LocatorObject = { locator: '.send', method: 'css' }; @@ -30,6 +31,26 @@ export const navDetailsWalletDropdown: LocatorObject = { locator: '.NavDropdown_toggle', method: 'css', }; + +export const navDetailsWalletDropdownRow: LocatorObject = { + locator: '//button[contains(@class, "NavDropdownRow_head")]', + method: 'xpath' +}; + +export const switchToWallet = async (customWorld: Object, seekWalletName: string) => { + await customWorld.click(navDetailsWalletDropdown); + const wallets = await customWorld.findElements(navDetailsWalletDropdownRow); + for (const wallet of wallets) { + const nameElem = await wallet.findElement(By.css('.NavPlate_name')); + const foundName = await nameElem.getText(); + if (foundName === seekWalletName) { + await wallet.click(); + return; + } + } + throw new Error(`No wallet found with name ${seekWalletName}`); +} + export const navDetailsBuyButton: LocatorObject = { locator: '.NavDropdownContent_buyButton', method: 'css', diff --git a/packages/yoroi-extension/features/pages/walletTransactionsPage.js b/packages/yoroi-extension/features/pages/walletTransactionsPage.js index d422c0bfb5..394c8031d3 100644 --- a/packages/yoroi-extension/features/pages/walletTransactionsPage.js +++ b/packages/yoroi-extension/features/pages/walletTransactionsPage.js @@ -1,6 +1,16 @@ // @flow import type { LocatorObject } from '../support/webdriver'; +import { By } from 'selenium-webdriver'; + +export const getNotificationMessage = async (customWorld: any, translatedMessage: string) => { + const messageParentElement = await customWorld.driver.findElement( + By.xpath('//div[contains(@role, "tooltip")]') + ); + return await messageParentElement.findElement( + By.xpath(`//span[contains(text(), "${translatedMessage}")]`) + ); +} export const walletSummaryBox: LocatorObject = { locator: 'walletSummary_box', method: 'id' }; export const walletSummaryComponent: LocatorObject = { diff --git a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js index 6f3afb5fd1..e6fa1d4925 100644 --- a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js +++ b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js @@ -1,26 +1,20 @@ // @flow import { Given, When, Then } from 'cucumber'; -import { By } from 'selenium-webdriver'; import { expect } from 'chai'; -import { termsOfUseComponent } from '../pages/basicSetupPage'; - -const TERMS_OF_USE_FORM = '.TermsOfUseForm_component'; +import { continueButton, getTosCheckbox, termsOfUseComponent } from '../pages/basicSetupPage'; Given(/^I am on the "Terms of use" screen$/, async function () { await this.waitForElement(termsOfUseComponent); }); When(/^I click on "I agree with the terms of use" checkbox$/, async function () { - const tosClassElement = await this.driver.findElement(By.css(TERMS_OF_USE_FORM)); - const checkbox = await tosClassElement.findElement(By.xpath('//input[@type="checkbox"]')); + const checkbox = await getTosCheckbox(); await checkbox.click(); }); When(/^I submit the "Terms of use" form$/, async function () { - const TOSComponent = await this.driver.findElement(By.css('.TermsOfUseForm_checkbox')); - const continueButton = await TOSComponent.findElement(By.xpath('//button')); - await continueButton.click(); + await this.click(continueButton); }); Then(/^I should not see the "Terms of use" screen anymore$/, async function () { diff --git a/packages/yoroi-extension/features/step_definitions/main-ui-steps.js b/packages/yoroi-extension/features/step_definitions/main-ui-steps.js index c6a479b9e8..f35fe88ade 100644 --- a/packages/yoroi-extension/features/step_definitions/main-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/main-ui-steps.js @@ -11,11 +11,16 @@ import { navDetailsBuyButton, navDetailsHideButton, navDetailsWalletDropdown, + switchToWallet, transactionsTab, } from '../pages/walletPage'; import { amountInput, receiverInput } from '../pages/walletSendPage'; import { walletButtonClassic } from '../pages/sidebarPage'; -import { copyToClipboardButton, walletSummaryComponent } from '../pages/walletTransactionsPage'; +import { + copyToClipboardButton, + getNotificationMessage, + walletSummaryComponent, +} from '../pages/walletTransactionsPage'; import { maintenanceBody, serverErrorBanner } from '../pages/mainWindowPage'; Then(/^I should see the balance number "([^"]*)"$/, async function (number) { @@ -46,12 +51,7 @@ Then(/^I click on "copy to clipboard" button$/, async function () { Then(/^I should see "copied" tooltip message:$/, async function (data) { const notification = data.hashes()[0]; const notificationMessage = await this.intl(notification.message); - const messageParentElement = await this.driver.findElement( - By.xpath('//div[contains(@role, "tooltip")]') - ); - const message = await messageParentElement.findElement( - By.xpath(`//span[contains(text(), "${notificationMessage}")]`) - ); + const message = await getNotificationMessage(this, notificationMessage); expect(await message.isDisplayed()).to.be.true; }); @@ -86,19 +86,7 @@ Then(/^I should see my balance hidden$/, async function () { }); Then(/^I switch to "([^"]*)" from the dropdown$/, async function (walletName) { - await this.click(navDetailsWalletDropdown); - const wallets = await this.driver.findElements( - By.xpath("//button[contains(@class, 'NavDropdownRow_head')]") - ); - for (const wallet of wallets) { - const nameElem = await wallet.findElement(By.css('.NavPlate_name')); - const foundName = await nameElem.getText(); - if (walletName === foundName) { - await wallet.click(); - return; - } - } - throw new Error(`No wallet found with name ${walletName}`); + await switchToWallet(this, walletName); }); Then(/^I select buy-sell from the dropdown$/, async function () { From 7c2fac1a28fb5cb8320e2dead29646d87e4730f0 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 16 Sep 2022 14:43:16 +0300 Subject: [PATCH 078/199] Updated memo-steps.js, walletSendPage.js --- .../features/pages/walletSendPage.js | 31 +++++++++++++++++++ .../features/step_definitions/memo-steps.js | 25 ++++++--------- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js index c266ef1e69..5ca6ee6c86 100644 --- a/packages/yoroi-extension/features/pages/walletSendPage.js +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -1,6 +1,7 @@ // @flow import type { LocatorObject } from '../support/webdriver'; +import { By } from 'selenium-webdriver'; export const assetSelector: LocatorObject = { locator: '.WalletSendForm_component .SimpleInput_input', @@ -13,10 +14,40 @@ export const assetListElement: LocatorObject = { export const receiverInput: LocatorObject = { locator: "input[name='receiver']", method: 'css' }; export const amountInput: LocatorObject = { locator: "input[name='amount']", method: 'css' }; export const addMemoButton: LocatorObject = { locator: '.addMemoButton', method: 'css' }; +export const memoDialogComponent: LocatorObject = { + locator: '.MemoDialogCommon_component', + method: 'css', +}; +export const memoContentText: LocatorObject = { locator: '.memoContent', method: 'css' }; +export const getMemoText = async (customWorld: Object) => { + const memoElem = await customWorld.getElementsBy(memoContentText); + return await memoElem[0].getText(); +}; export const memoContentInput: LocatorObject = { locator: "input[name='memo']", method: 'css', }; +export const editMemoButton: LocatorObject = { locator: '.editMemoButton', method: 'css' }; +export const deleteMemo = async (customWorld: Object, confirmDeleting: boolean = true) => { + let memoComponent = await customWorld.findElement(memoDialogComponent); + const deleteButton = await memoComponent.findElement( + By.xpath('//button[@aria-label="delete memo"]') + ); + await deleteButton.click(); + memoComponent = await customWorld.findElement(memoDialogComponent); + if (confirmDeleting){ + const confirmDelete = await memoComponent.findElement( + By.xpath('//button[contains(text(), "Delete")]') + ); + await confirmDelete.click(); + } else { + const confirmDelete = await memoComponent.findElement( + By.xpath('//button[contains(text(), "Cancel")]') + ); + await confirmDelete.click(); + } +} + export const nextButton: LocatorObject = { locator: '.WalletSendForm_component .MuiButton-primary', method: 'css', diff --git a/packages/yoroi-extension/features/step_definitions/memo-steps.js b/packages/yoroi-extension/features/step_definitions/memo-steps.js index 11bfe636b3..11e8b6c9f7 100644 --- a/packages/yoroi-extension/features/step_definitions/memo-steps.js +++ b/packages/yoroi-extension/features/step_definitions/memo-steps.js @@ -1,11 +1,16 @@ // @flow import { Then } from 'cucumber'; -import { By } from 'selenium-webdriver'; import chai from 'chai'; import { MAX_MEMO_SIZE } from '../../app/config/externalStorageConfig'; import { primaryButton } from '../pages/commonDialogPage'; -import { addMemoButton, memoContentInput } from '../pages/walletSendPage'; +import { + addMemoButton, + deleteMemo, + editMemoButton, + getMemoText, + memoContentInput, +} from '../pages/walletSendPage'; Then(/^I add a memo that says "([^"]*)"$/, async function (memo) { await this.click(addMemoButton); @@ -16,8 +21,7 @@ Then(/^I add a memo that says "([^"]*)"$/, async function (memo) { Then(/^The memo content says "([^"]*)"$/, async function (memo) { await this.waitForElement({ locator: '.memoContent', method: 'css' }); - const memoElem = await this.getElementsBy({ locator: '.memoContent', method: 'css' }); - const memoContent = await memoElem[0].getText(); + const memoContent = await getMemoText(this); chai.expect(memoContent).to.equal(memo); }); @@ -30,18 +34,9 @@ Then(/^I edit the memo to say "([^"]*)"$/, async function (memo) { }); Then(/^I delete the memo$/, async function () { - await this.click({ locator: '.editMemoButton', method: 'css' }); + await this.click(editMemoButton); await this.click(primaryButton); - let memoComponent = await this.driver.findElement(By.css('.MemoDialogCommon_component')); - const deleteButton = await memoComponent.findElement( - By.xpath('//button[@aria-label="delete memo"]') - ); - await deleteButton.click(); - memoComponent = await this.driver.findElement(By.css('.MemoDialogCommon_component')); - const confirmDelete = await memoComponent.findElement( - By.xpath('//button[contains(text(), "Delete")]') - ); - await confirmDelete.click(); + await deleteMemo(this); }); Then(/^There is no memo for the transaction$/, async function () { From 07f9ef93d4fb9205ebf18c519e6a9f7bbc100984 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 16 Sep 2022 14:47:55 +0300 Subject: [PATCH 079/199] Updated basicSetupPage.js, select-language-steps.js --- .../yoroi-extension/features/pages/basicSetupPage.js | 10 +++++++++- .../features/step_definitions/select-language-steps.js | 10 ++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/yoroi-extension/features/pages/basicSetupPage.js b/packages/yoroi-extension/features/pages/basicSetupPage.js index 54e46885fb..47b17ec461 100644 --- a/packages/yoroi-extension/features/pages/basicSetupPage.js +++ b/packages/yoroi-extension/features/pages/basicSetupPage.js @@ -3,11 +3,19 @@ import type { LocatorObject } from '../support/webdriver'; import { By } from 'selenium-webdriver'; +const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; + // language select page export const languageSelectionForm: LocatorObject = { - locator: '.LanguageSelectionForm_component', + locator: LANGUAGE_SELECTION_FORM, method: 'css', }; + +export const languageSelectionFromDropdown: LocatorObject = { + locator: `${LANGUAGE_SELECTION_FORM} .MuiInputBase-input`, + method: 'css', +}; + export const japaneseLaguageSelection: LocatorObject = { locator: '//span[contains(text(), "日本語")]', method: 'xpath', diff --git a/packages/yoroi-extension/features/step_definitions/select-language-steps.js b/packages/yoroi-extension/features/step_definitions/select-language-steps.js index 6be98a1e7c..273d0a564f 100644 --- a/packages/yoroi-extension/features/step_definitions/select-language-steps.js +++ b/packages/yoroi-extension/features/step_definitions/select-language-steps.js @@ -3,9 +3,11 @@ import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; import languageSelection, { clickContinue } from '../support/helpers/language-selection-helpers'; -import { japaneseLaguageSelection, languageSelectionForm } from '../pages/basicSetupPage'; - -const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; +import { + japaneseLaguageSelection, + languageSelectionForm, + languageSelectionFromDropdown, +} from '../pages/basicSetupPage'; Given(/^I have selected English language$/, async function () { await languageSelection.ensureLanguageIsSelected(this, { language: 'en-US' }); @@ -16,7 +18,7 @@ When(/^I am on the language selection screen$/, async function () { }); When(/^I open language selection dropdown$/, async function () { - await this.click({ locator: `${LANGUAGE_SELECTION_FORM} .MuiInputBase-input`, method: 'css' }); + await this.click(languageSelectionFromDropdown); }); When(/^I select Japanese language$/, async function () { From 8e75cb10991abef7eb29de99aee736f843bd146d Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 16 Sep 2022 15:03:46 +0300 Subject: [PATCH 080/199] Updated commonDialogPage.js, settingsPage.js, settings-ui-steps.js --- .../features/pages/commonDialogPage.js | 11 +++++++++++ .../yoroi-extension/features/pages/settingsPage.js | 4 ++++ .../features/step_definitions/settings-ui-steps.js | 13 +++++-------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/yoroi-extension/features/pages/commonDialogPage.js b/packages/yoroi-extension/features/pages/commonDialogPage.js index 4c1c6b8fa0..c14598a576 100644 --- a/packages/yoroi-extension/features/pages/commonDialogPage.js +++ b/packages/yoroi-extension/features/pages/commonDialogPage.js @@ -1,6 +1,7 @@ // @flow import type { LocatorObject } from '../support/webdriver'; +import { By } from 'selenium-webdriver'; export const primaryButton: LocatorObject = { locator: '.primary', method: 'css' }; export const errorBlockComponent: LocatorObject = { @@ -8,3 +9,13 @@ export const errorBlockComponent: LocatorObject = { method: 'css', }; export const dialogTitle: LocatorObject = { locator: '.dialog__title', method: 'css' }; + +export const warningCheckboxElement: LocatorObject = { + locator: '.DangerousActionDialog_checkbox', + method: 'css', +}; + +export const getWarningCheckbox = async (customWorld: Object) => { + const warningCheckboxComponent = await customWorld.findElement(warningCheckboxElement); + return await warningCheckboxComponent.findElement(By.xpath('//input[@type="checkbox"]')); +} diff --git a/packages/yoroi-extension/features/pages/settingsPage.js b/packages/yoroi-extension/features/pages/settingsPage.js index 1deecc41d6..a8718357d1 100644 --- a/packages/yoroi-extension/features/pages/settingsPage.js +++ b/packages/yoroi-extension/features/pages/settingsPage.js @@ -64,6 +64,10 @@ export const exportPublicKeyDialog: LocatorObject = { locator: '.ExportPublicKeyDialog_component', method: 'css', }; +export const exportPublicKeyText: LocatorObject = { + locator: '.CodeBlock_component', + method: 'css', +}; // Level of complexity tab diff --git a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js index b18e47b66d..eacd19015d 100644 --- a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js @@ -2,7 +2,7 @@ import { When, Given, Then } from 'cucumber'; import i18n from '../support/helpers/i18n-helpers'; -import { By, Key } from 'selenium-webdriver'; +import { Key } from 'selenium-webdriver'; import { truncateLongName } from '../../app/utils/formatters'; import { expect } from 'chai'; import { checkErrorByTranslationId } from './common-steps'; @@ -30,8 +30,9 @@ import { exportButton, exportPublicKeyDialog, fullScreenMessage, + exportPublicKeyText, } from '../pages/settingsPage'; -import { dialogTitle } from '../pages/commonDialogPage'; +import { dialogTitle, getWarningCheckbox } from '../pages/commonDialogPage'; Given(/^I should see the "([^"]*)" wallet password dialog$/, async function (dialogType) { const selector = '.' + dialogType + 'PasswordDialog'; @@ -154,16 +155,12 @@ When(/^I click on export wallet$/, async function () { Then(/^I should see the wallet export for key "([^"]*)"$/, async function (expectedKey) { await this.waitForElement(exportPublicKeyDialog); - const publicKeyForm = await this.driver.findElement(By.css('.CodeBlock_component')); - const publicKey = await publicKeyForm.getText(); + const publicKey = await this.getText(exportPublicKeyText); expect(publicKey).to.equal(expectedKey); }); Then(/^I click on the checkbox$/, async function () { - const warningCheckboxElement = await this.driver.findElement( - By.css('.DangerousActionDialog_checkbox') - ); - const checkbox = await warningCheckboxElement.findElement(By.xpath('//input[@type="checkbox"]')); + const checkbox = await getWarningCheckbox(this); await checkbox.click(); }); From 89f79d4c36d0f4b9d5e3fb26b5ad151ba317207e Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 16 Sep 2022 22:03:25 +0300 Subject: [PATCH 081/199] Updated several files --- .../features/pages/newWalletPages.js | 7 +++ .../features/pages/walletTransactionsPage.js | 17 ++++++ .../features/pages/walletVotingPage.js | 4 ++ .../step_definitions/tx-history-steps.js | 59 ++----------------- .../features/step_definitions/voting-steps.js | 3 +- .../step_definitions/wallet-creation-steps.js | 2 +- .../step_definitions/wallet-paper-steps.js | 18 +++--- .../support/helpers/common-constants.js | 45 ++++++++++++++ 8 files changed, 88 insertions(+), 67 deletions(-) diff --git a/packages/yoroi-extension/features/pages/newWalletPages.js b/packages/yoroi-extension/features/pages/newWalletPages.js index d77ff91ffc..5cfba98fd1 100644 --- a/packages/yoroi-extension/features/pages/newWalletPages.js +++ b/packages/yoroi-extension/features/pages/newWalletPages.js @@ -142,6 +142,9 @@ export const restoreDialogButton: LocatorObject = { locator: '.WalletRestoreDialog .primary', method: 'css', }; +export const getAddressesAmountButton = (addressesAmount: string): LocatorObject => { + return { locator: `//li[contains(text(), "${addressesAmount}")]`, method: 'xpath' }; +}; export const recoveryPhraseDeleteIcon = { locator: `(//span[contains(text(), '×')])[1]`, method: 'xpath', @@ -150,6 +153,10 @@ export const recoveryPhraseError: LocatorObject = { locator: '//p[starts-with(@id, "recoveryPhrase--")]', method: 'xpath', }; +export const addressElement : LocatorObject = { + locator: '//span[contains(@class, "RawHash_hash")]', + method: 'xpath', +}; // Common elements export const walletNameInput: LocatorObject = { diff --git a/packages/yoroi-extension/features/pages/walletTransactionsPage.js b/packages/yoroi-extension/features/pages/walletTransactionsPage.js index 394c8031d3..d1794d121f 100644 --- a/packages/yoroi-extension/features/pages/walletTransactionsPage.js +++ b/packages/yoroi-extension/features/pages/walletTransactionsPage.js @@ -12,6 +12,19 @@ export const getNotificationMessage = async (customWorld: any, translatedMessage ); } +export const parseTxInfo = async (addressList: Array): Promise> => { + const addressInfoRow = await addressList.findElements(By.css('.Transaction_addressItem')); + + const result = []; + for (const row of addressInfoRow) { + const rowInfo = await row.findElements(By.xpath('*')); + const rowInfoText = await Promise.all(rowInfo.map(async column => await column.getText())); + result.push(rowInfoText); + } + + return result; +} + export const walletSummaryBox: LocatorObject = { locator: 'walletSummary_box', method: 'id' }; export const walletSummaryComponent: LocatorObject = { locator: '.WalletSummary_component', @@ -45,3 +58,7 @@ export const failedTransactionElement: LocatorObject = { locator: '.Transaction_failedLabel', method: 'css', }; +export const transactionAddressListElement: LocatorObject = { + locator: '.Transaction_addressList', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/walletVotingPage.js b/packages/yoroi-extension/features/pages/walletVotingPage.js index 8398f80f8b..155f39ae97 100644 --- a/packages/yoroi-extension/features/pages/walletVotingPage.js +++ b/packages/yoroi-extension/features/pages/walletVotingPage.js @@ -14,6 +14,10 @@ export const generatedPinButton: LocatorObject = { locator: '.GeneratePinDialog_dialog > .Dialog_actions > .primary', method: 'css', }; +export const generatedPinStepElement: LocatorObject = { + locator: '.GeneratePinDialog_pin > span', + method: 'css', +}; export const confirmPinDialog: LocatorObject = { locator: '.ConfirmPinDialog_dialog', method: 'css', diff --git a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js index 915b43eb3f..4829046add 100644 --- a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js +++ b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js @@ -9,11 +9,13 @@ import { failedTransactionElement, noTransactionsComponent, numberOfTransactions, + parseTxInfo, pendingTransactionElement, - showMoreButton, + showMoreButton, transactionAddressListElement, transactionListElement, } from '../pages/walletTransactionsPage'; import { summaryTab } from '../pages/walletPage'; +import { displayInfo } from '../support/helpers/common-constants'; function verifyAllTxsFields( txType, @@ -113,19 +115,6 @@ When(/^I expand the top transaction$/, async function () { await topTx.click(); }); -async function parseTxInfo(addressList) { - const addressInfoRow = await addressList.findElements(By.css('.Transaction_addressItem')); - - const result = []; - for (const row of addressInfoRow) { - const rowInfo = await row.findElements(By.xpath('*')); - const rowInfoText = await Promise.all(rowInfo.map(async column => await column.getText())); - result.push(rowInfoText); - } - - return result; -} - Then(/^I verify top transaction content ([^"]*)$/, async function (walletName) { await this.waitForElement(transactionListElement); const actualTxsList = await this.getElementsBy(transactionListElement); @@ -144,7 +133,7 @@ Then(/^I verify top transaction content ([^"]*)$/, async function (walletName) { await topTx.click(); - const txList = await topTx.findElements(By.css('.Transaction_addressList')); + const txList = await topTx.findElements(transactionAddressListElement); const fromTxInfo = await parseTxInfo(txList[0]); const toTxInfo = await parseTxInfo(txList[1]); @@ -190,46 +179,6 @@ Then(/^The number of confirmations of the top tx is ([^"]*)$/, async function (c chai.expect(confirmationCount).to.equal(count); }); -const displayInfo = { - 'many-tx-wallet': { - txType: 'ADA intrawallet transaction', - txAmount: '-0.169999', - txTime: '2019-04-21T15:13:33.000Z', - txStatus: 'HIGH', - txFrom: [['Ae2tdPwUPE...VWfitHfUM9', 'BYRON - INTERNAL', '-0.82 ADA']], - txTo: [ - ['Ae2tdPwUPE...iLjTnt34Aj', 'BYRON - EXTERNAL', '+0.000001 ADA'], - ['Ae2tdPwUPE...BA7XbSMhKd', 'BYRON - INTERNAL', '+0.65 ADA'], - ], - txId: '0a073669845fea4ae83cd4418a0b4fd56610097a89601a816b5891f667e3496c', - txConfirmations: 'High. 104 confirmations.', - txFee: '0.169999', - }, - 'simple-pending-wallet': { - txType: 'ADA intrawallet transaction', - txAmount: '-0.999999', - txTime: '2019-04-20T23:14:52.000Z', - txStatus: 'PENDING', - txFrom: [['Ae2tdPwUPE...e1cT2aGdSJ', 'BYRON - EXTERNAL', '-1 ADA']], - txTo: [['Ae2tdPwUPE...sTrQfTxPVX', 'PROCESSING...', '+0.000001 ADA']], - txId: 'fa6f2c82fb511d0cc9c12a540b5fac6e5a9b0f288f2d140f909f981279e16fbe', - txFee: '0.999999', - }, - 'failed-single-tx': { - txType: 'ADA sent', - txAmount: '-0.18', - txTime: '2019-04-20T23:14:51.000Z', - txStatus: 'FAILED', - txFrom: [['Ae2tdPwUPE...gBfkkDNBNv', 'BYRON - EXTERNAL', '-1 ADA']], - txTo: [ - ['Ae2tdPwUPE...xJPmFzi6G2', 'ADDRESS BOOK', '+0.000001 ADA'], - ['Ae2tdPwUPE...bL4UYPN3eU', 'BYRON - INTERNAL', '+0.82 ADA'], - ], - txId: 'fc6a5f086c0810de3048651ddd9075e6e5543bf59cdfe5e0c73bf1ed9dcec1ab', - txFee: '0.179999', - }, -}; - When(/^I go to the tx history screen$/, async function () { await this.click(summaryTab); }); diff --git a/packages/yoroi-extension/features/step_definitions/voting-steps.js b/packages/yoroi-extension/features/step_definitions/voting-steps.js index d0fd1c934d..e8973f6bdb 100644 --- a/packages/yoroi-extension/features/step_definitions/voting-steps.js +++ b/packages/yoroi-extension/features/step_definitions/voting-steps.js @@ -9,6 +9,7 @@ import { confirmPinDialogError, errorBlock, generatedPinButton, + generatedPinStepElement, generatePinDialog, pinInput, qrCodeDialog, @@ -29,7 +30,7 @@ When(/^I click on the register button in the voting page$/, async function () { }); Then(/^I see the Auto generated Pin Steps$/, async function () { - const elements = await this.driver.findElements(By.css('.GeneratePinDialog_pin > span ')); + const elements = await this.findElements(generatedPinStepElement); const pin = []; for (const item of elements) { diff --git a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js index 69f3bd8cae..fb4c50d988 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js @@ -80,7 +80,7 @@ When(/^I accept the creation terms$/, async function () { By.xpath('//div[contains(@class,"WalletBackupPrivacyWarningDialog_component")]') ); const privacyChkbox = privacyDlg.findElement(By.xpath('//input[@type="checkbox"]')); - privacyChkbox.click(); + await privacyChkbox.click(); await this.click(continueButton); }); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js index 5c204a9d6a..9f612d3b76 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js @@ -1,12 +1,16 @@ // @flow import { Given, Then } from 'cucumber'; -import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import { truncateAddress } from '../../app/utils/formatters'; import { enterRecoveryPhrase } from '../support/helpers/helpers'; import { primaryButton } from '../pages/commonDialogPage'; -import { paperWalletDialogSelect } from '../pages/newWalletPages'; +import { + addressElement, + getAddressesAmountButton, + paperWalletDialogSelect, +} from '../pages/newWalletPages'; +import { fakeAddresses } from '../support/helpers/common-constants'; // ========== Paper wallet ========== @@ -15,17 +19,13 @@ Then(/^I open Number of Adddresses selection dropdown$/, async function () { }); Then(/^I select 2 addresses$/, async function () { - return this.click({ locator: '//li[contains(text(), "2")]', method: 'xpath' }); + return this.click(getAddressesAmountButton('2')); }); Then(/^I click the create paper wallet button$/, async function () { await this.click(primaryButton); }); -const fakeAddresses = [ - 'Ae2tdPwUPEZBxVncTviWLPFDukXL7tDWfGXkLMw8CSjqZhPGB7SHkUFaASB', - 'Ae2tdPwUPEZKTSRpuAt5GhVda8ZAoPXHTXzX9xSP2Ms7YyakwAyREYBpR11', -]; Then(/^I enter the paper recovery phrase$/, async function () { /** * Mnemomic is printed on the paper wallet and not present in the UI @@ -52,9 +52,7 @@ Given(/^I swap the paper wallet addresses$/, async function () { }); Then(/^I should see two addresses displayed$/, async function () { - const addressesElem = await this.driver.findElements( - By.xpath("//span[contains(@class, 'RawHash_hash')]") - ); + const addressesElem = await this.findElements(addressElement); expect(addressesElem.length).to.be.equal(fakeAddresses.length); for (let i = 0; i < fakeAddresses.length; i++) { const address = await addressesElem[i].getText(); diff --git a/packages/yoroi-extension/features/support/helpers/common-constants.js b/packages/yoroi-extension/features/support/helpers/common-constants.js index 19ed3b4ccc..995618d8f6 100644 --- a/packages/yoroi-extension/features/support/helpers/common-constants.js +++ b/packages/yoroi-extension/features/support/helpers/common-constants.js @@ -13,3 +13,48 @@ export const emailOptions = { url: `https://mailsac.com/api/addresses/${mailsacEmail}/messages`, headers: { 'Mailsac-Key': mailsacAPIKey }, }; + +export const displayInfo = { + 'many-tx-wallet': { + txType: 'ADA intrawallet transaction', + txAmount: '-0.169999', + txTime: '2019-04-21T15:13:33.000Z', + txStatus: 'HIGH', + txFrom: [['Ae2tdPwUPE...VWfitHfUM9', 'BYRON - INTERNAL', '-0.82 ADA']], + txTo: [ + ['Ae2tdPwUPE...iLjTnt34Aj', 'BYRON - EXTERNAL', '+0.000001 ADA'], + ['Ae2tdPwUPE...BA7XbSMhKd', 'BYRON - INTERNAL', '+0.65 ADA'], + ], + txId: '0a073669845fea4ae83cd4418a0b4fd56610097a89601a816b5891f667e3496c', + txConfirmations: 'High. 104 confirmations.', + txFee: '0.169999', + }, + 'simple-pending-wallet': { + txType: 'ADA intrawallet transaction', + txAmount: '-0.999999', + txTime: '2019-04-20T23:14:52.000Z', + txStatus: 'PENDING', + txFrom: [['Ae2tdPwUPE...e1cT2aGdSJ', 'BYRON - EXTERNAL', '-1 ADA']], + txTo: [['Ae2tdPwUPE...sTrQfTxPVX', 'PROCESSING...', '+0.000001 ADA']], + txId: 'fa6f2c82fb511d0cc9c12a540b5fac6e5a9b0f288f2d140f909f981279e16fbe', + txFee: '0.999999', + }, + 'failed-single-tx': { + txType: 'ADA sent', + txAmount: '-0.18', + txTime: '2019-04-20T23:14:51.000Z', + txStatus: 'FAILED', + txFrom: [['Ae2tdPwUPE...gBfkkDNBNv', 'BYRON - EXTERNAL', '-1 ADA']], + txTo: [ + ['Ae2tdPwUPE...xJPmFzi6G2', 'ADDRESS BOOK', '+0.000001 ADA'], + ['Ae2tdPwUPE...bL4UYPN3eU', 'BYRON - INTERNAL', '+0.82 ADA'], + ], + txId: 'fc6a5f086c0810de3048651ddd9075e6e5543bf59cdfe5e0c73bf1ed9dcec1ab', + txFee: '0.179999', + }, +}; + +export const fakeAddresses = [ + 'Ae2tdPwUPEZBxVncTviWLPFDukXL7tDWfGXkLMw8CSjqZhPGB7SHkUFaASB', + 'Ae2tdPwUPEZKTSRpuAt5GhVda8ZAoPXHTXzX9xSP2Ms7YyakwAyREYBpR11', +]; \ No newline at end of file From f4acf5c51a7ec9895f30d3e564b0f443f57e40da Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 20 Sep 2022 13:48:37 +0200 Subject: [PATCH 082/199] Improve QR code image quality (750px) --- .../app/components/wallet/voting/QrCodeDialog.js | 2 +- .../app/components/wallet/voting/QrCodeDialog.scss | 5 +++++ .../dialogs/voting/VotingRegistrationDialogContainer.js | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/voting/QrCodeDialog.js b/packages/yoroi-extension/app/components/wallet/voting/QrCodeDialog.js index 98e7dc6779..b00ca6b2d6 100644 --- a/packages/yoroi-extension/app/components/wallet/voting/QrCodeDialog.js +++ b/packages/yoroi-extension/app/components/wallet/voting/QrCodeDialog.js @@ -105,7 +105,7 @@ export default class QrCodeDialog extends Component {
    let component = null; - switch (votingStore.progressInfo.currentStep) { + // switch (votingStore.progressInfo.currentStep) { + switch (ProgressStep.QR_CODE) { case ProgressStep.GENERATE: component = ( Date: Tue, 20 Sep 2022 13:52:26 +0200 Subject: [PATCH 083/199] lint + fixed styles --- .../components/connect/ConnectPage.js | 143 +++++++++--------- .../components/connect/ConnectPage.scss | 13 +- .../components/connect/WalletCard.scss | 9 +- 3 files changed, 85 insertions(+), 80 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js index dbe6337190..01aa7d7457 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js @@ -27,11 +27,11 @@ import TextField from '../../../components/common/TextField'; import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import config from '../../../config'; import vjf from 'mobx-react-form/lib/validators/VJF'; -import { WrongPassphraseError } from '../../../api/ada/lib/cardanoCrypto/cryptoErrors' -import { ReactComponent as NoWalletImage } from '../../assets/images/no-websites-connected.inline.svg' -import { ReactComponent as NoDappIcon } from '../../../assets/images/dapp-connector/no-dapp.inline.svg'; -import { ReactComponent as IconEyeOpen } from '../../../assets/images/my-wallets/icon_eye_open.inline.svg'; -import { ReactComponent as IconEyeClosed } from '../../../assets/images/my-wallets/icon_eye_closed.inline.svg' +import { WrongPassphraseError } from '../../../api/ada/lib/cardanoCrypto/cryptoErrors'; +import { ReactComponent as NoWalletImage } from '../../assets/images/no-websites-connected.inline.svg'; +import { ReactComponent as NoDappIcon } from '../../../assets/images/dapp-connector/no-dapp.inline.svg'; +import { ReactComponent as IconEyeOpen } from '../../../assets/images/my-wallets/icon_eye_open.inline.svg'; +import { ReactComponent as IconEyeClosed } from '../../../assets/images/my-wallets/icon_eye_closed.inline.svg'; const messages = defineMessages({ subtitle: { @@ -44,7 +44,8 @@ const messages = defineMessages({ }, connectWalletAuthRequest: { id: 'ergo-connector.label.connectWalletAuthRequest', - defaultMessage: '!!!The dApp requests to use your wallet identity for authentication. Enter your spending password to confirm.', + defaultMessage: + '!!!The dApp requests to use your wallet identity for authentication. Enter your spending password to confirm.', }, connectWalletNoHardwareSupported: { id: 'ergo-connector.label.connectWalletNoHardwareSupported', @@ -141,9 +142,9 @@ class ConnectPage extends Component { ); hidePasswordForm: void => void = () => { - this.form.$('walletPassword').clear() - this.props.hidePasswordForm() - } + this.form.$('walletPassword').clear(); + this.props.hidePasswordForm(); + }; submit: void => void = () => { this.form.submit({ @@ -153,9 +154,9 @@ class ConnectPage extends Component { if (deriver && checksum) { this.props.onConnect(deriver, checksum, walletPassword).catch(error => { if (error instanceof WrongPassphraseError) { - this.form.$('walletPassword').invalidate( - this.context.intl.formatMessage(messages.incorrectWalletPasswordError) - ) + this.form + .$('walletPassword') + .invalidate(this.context.intl.formatMessage(messages.incorrectWalletPasswordError)); } else { throw error; } @@ -172,11 +173,11 @@ class ConnectPage extends Component { onCreateWallet: void => void = () => { window.chrome.tabs.create({ - url: `${window.location.origin}/main_window.html#/wallets/add` - }) + url: `${window.location.origin}/main_window.html#/wallets/add`, + }); - this.props.onCancel() - } + this.props.onCancel(); + }; render(): Node { const { intl } = this.context; @@ -217,7 +218,7 @@ class ConnectPage extends Component {
    - ) + ); } const walletPasswordField = this.form.$('walletPassword'); @@ -229,7 +230,7 @@ class ConnectPage extends Component {
    { />
    -
    - ) : hasWallets ? ( - -
    - - {intl.formatMessage(messages.yourWallets)} - - -
    -
      - {publicDerivers.map(item => ( -
    • + {publicDerivers.map(item => ( +
    • + onSelectWallet(item.publicDeriver, item.checksum)} > - onSelectWallet(item.publicDeriver, item.checksum)} - > - - -
    • - ))} -
    -
    - ) : null} - + + + + ))} + + + ) : null} )} - { hasWallets && !isAppAuth ? + {hasWallets && !isAppAuth ? (
    -

    +

    {intl.formatMessage(messages.connectWalletNoHardwareSupported)}

    -

    - {intl.formatMessage(messages.connectInfo)} -

    +

    {intl.formatMessage(messages.connectInfo)}

    {intl.formatMessage(connectorMessages.messageReadOnly)}

    - -
    : null } +
    + ) : null}
    ); } diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss index 4d827ca966..d35959780a 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.scss @@ -34,14 +34,19 @@ } } - .titleWallet { display: flex; + margin-left: 20px; + & * + * { padding-left: 10px; } } +.toggleButton { + cursor: pointer; +} + .noWallets { display: flex; align-items: center; @@ -64,7 +69,7 @@ } .createWallet { - color: #17D1AA; + color: #17d1aa; font-size: 16px; line-height: 24px; cursor: pointer; @@ -77,8 +82,6 @@ .list { & .listItem { - padding-top: 15px; - padding-bottom: 15px; & > div { width: 100%; } @@ -109,7 +112,6 @@ } } - @media (min-width: 678px) { .component { max-width: 600px; @@ -122,4 +124,3 @@ } } } - diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.scss b/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.scss index f6703b15fc..8dc075cd2f 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.scss +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.scss @@ -5,6 +5,13 @@ width: 100%; letter-spacing: 0; line-height: 22px; + padding: 18px; + border: 2px solid #fff; + border-radius: 10px; + + &:hover { + border-color: var(--yoroi-palette-gray-50); + } & .avatar { margin-right: 13px; @@ -32,6 +39,6 @@ } & .balance { - font-weight: 500; + font-weight: 400; } } From d6f96a943d9bcf51dfe96d607bc9a1b3cd1738d6 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 20 Sep 2022 13:54:17 +0200 Subject: [PATCH 084/199] Revert commented code --- .../app/components/wallet/voting/QrCodeDialog.js | 2 +- .../wallet/dialogs/voting/VotingRegistrationDialogContainer.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/voting/QrCodeDialog.js b/packages/yoroi-extension/app/components/wallet/voting/QrCodeDialog.js index b00ca6b2d6..9f472e7182 100644 --- a/packages/yoroi-extension/app/components/wallet/voting/QrCodeDialog.js +++ b/packages/yoroi-extension/app/components/wallet/voting/QrCodeDialog.js @@ -69,7 +69,7 @@ export default class QrCodeDialog extends Component { { label: intl.formatMessage(messages.downloadQrCode), primary: true, - onClick: () => downloadQrCode(QR_ID, 'Voting key.png'), + onClick: () => downloadQrCode(QR_ID, 'Voting key'), } ]; diff --git a/packages/yoroi-extension/app/containers/wallet/dialogs/voting/VotingRegistrationDialogContainer.js b/packages/yoroi-extension/app/containers/wallet/dialogs/voting/VotingRegistrationDialogContainer.js index 8944895b97..0a18829143 100644 --- a/packages/yoroi-extension/app/containers/wallet/dialogs/voting/VotingRegistrationDialogContainer.js +++ b/packages/yoroi-extension/app/containers/wallet/dialogs/voting/VotingRegistrationDialogContainer.js @@ -65,8 +65,7 @@ export default class VotingRegistrationDialogContainer extends Component let component = null; - // switch (votingStore.progressInfo.currentStep) { - switch (ProgressStep.QR_CODE) { + switch (votingStore.progressInfo.currentStep) { case ProgressStep.GENERATE: component = ( Date: Mon, 4 Jul 2022 18:11:42 +0800 Subject: [PATCH 085/199] fix hints for multi-asset sending --- .../img/nano-s/hint-asset-fingerprint.png | Bin 0 -> 698635 bytes .../assets/img/nano-s/hint-token-amount.png | Bin 0 -> 696996 bytes .../img/nano-x/hint-asset-fingerprint.png | Bin 0 -> 1055212 bytes .../assets/img/nano-x/hint-token-amount.png | Bin 0 -> 1054993 bytes .../connect/operation/send/SendTxHintBlock.js | 54 +++++++++-- .../components/manual-test/TestBlock.js | 90 ++++++++++++++++++ .../ledger/i18n/locales/en-US.json | 2 + 7 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 packages/yoroi-extension/ledger/assets/img/nano-s/hint-asset-fingerprint.png create mode 100644 packages/yoroi-extension/ledger/assets/img/nano-s/hint-token-amount.png create mode 100644 packages/yoroi-extension/ledger/assets/img/nano-x/hint-asset-fingerprint.png create mode 100644 packages/yoroi-extension/ledger/assets/img/nano-x/hint-token-amount.png diff --git a/packages/yoroi-extension/ledger/assets/img/nano-s/hint-asset-fingerprint.png b/packages/yoroi-extension/ledger/assets/img/nano-s/hint-asset-fingerprint.png new file mode 100644 index 0000000000000000000000000000000000000000..f8b87304f9d028d68a8e7907628def9d64c60466 GIT binary patch literal 698635 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

    @@ -407,6 +410,93 @@ export default class TestBlock extends React.Component { console.debug(`[YLC] TEST:onSignTransaction`); } + onSignMultiAssetTransaction: () => void = () => { + if (this.state.visible === `${styles.visible}`) { + const inputs = [ + { + txHashHex: 'e3a768c5b3109fa3268d875316063809a298602a272d7933c2b4443b69058d7a', + outputIndex: 0, + path: utils.str_to_path("1852'/1815'/0'/0/0") + } + ]; + + const outputs = [ + { + amount: '700000', + destination: { + type: TxOutputDestinationType.THIRD_PARTY, + params: { + // Ae2tdPwUPEZCfyggUgSxD1E5UCx5f5hrXCdvQjJszxE7epyZ4ox9vRNUbHf + addressHex: '82d818582183581c9f01f38ec3af8341f45a301b075bfd6fd0cfbaddb01c5ebe780918b9a0001adb482c56', + }, + }, + tokenBundle: [ + { + policyIdHex: '16af70780a170994e8e5e575f4401b1d89bddf7d1a11d6264e0b0c85', + tokens: [ + { + amount: '1', + assetNameHex: '74426967546f6b656e4e616d653132' + } + ] + }, + { + policyIdHex: '2c9d0ecfc2ee1288056df15be4196d8ded73db345ea5b4cd5c7fac3f', + tokens: [ + { + amount: '1', + assetNameHex: '76737562737465737435' + } + ] + }, + { + policyIdHex: '6b8d07d69639e9413dd637a1a815a7323c69c86abbafb66dbfdb1aa7', + tokens: [ + { + amount: '2', + assetNameHex: '' + } + ] + } + ], + }, + { + destination: { + type: TxOutputDestinationType.DEVICE_OWNED, + params: { + type: AddressType.BASE_PAYMENT_KEY_STAKE_KEY, + params: { + spendingPath: utils.str_to_path("1852'/1815'/0'/0/0"), + stakingPath: utils.str_to_path("1852'/1815'/0'/2/0"), + }, + }, + }, + amount: '100000', + }, + ]; + + const req = this.makeRequest( + OPERATION_NAME.SIGN_TX, + ({ + signingMode: TransactionSigningMode.ORDINARY_TRANSACTION, + tx: { + network: { + networkId: MainnetIds.chainNetworkId, + protocolMagic: MainnetIds.protocolMagic, + }, + inputs, + outputs, + fee: '500', + ttl: '20', + }, + additionalWitnessPaths: [], + }: SignTransactionRequest) + ); + window.postMessage(req); + } + console.debug(`[YLC] TEST:onSignMultiAssetTransaction`); + } + onCatalystRegistrationSignTransaction: () => void = () => { if (this.state.visible === `${styles.visible}`) { const inputs = [ diff --git a/packages/yoroi-extension/ledger/i18n/locales/en-US.json b/packages/yoroi-extension/ledger/i18n/locales/en-US.json index 92df7d91b5..c97346164b 100644 --- a/packages/yoroi-extension/ledger/i18n/locales/en-US.json +++ b/packages/yoroi-extension/ledger/i18n/locales/en-US.json @@ -42,6 +42,8 @@ "hint.sendTx.confirmAddress": "Confirm the receiver's address by pressing both buttons.", "hint.nanoX.sendTx.confirmAddress": "Confirm the receiver's address by pressing the right button to scroll through the entire address. Then press both buttons.", "hint.pointer": "Make sure the pointer shown on your Ledger is the same as the one shown below, then press both buttons.", + "hint.sendTx.confirmAssetAmount": "Confirm the token amount by pressintg both buttons", + "hint.sendTx.confirmAssetFingerprint": "Confirm the asset fingerprint by pressintg both buttons", "hint.sendTx.confirmFee": "Confirm Transaction Fee by pressing both buttons.", "hint.sendTx.confirmTx": "Confirm the transaction by pressing the right button.", "hint.sendTx.ttl": "Confirm the time-to-live by pressing both buttons.", From 984cb22f2ed7e7e1a880e433ef583854756bfcdf Mon Sep 17 00:00:00 2001 From: yushi Date: Wed, 21 Sep 2022 13:42:40 +0800 Subject: [PATCH 086/199] fix typo --- .../components/connect/operation/send/SendTxHintBlock.js | 4 ++-- packages/yoroi-extension/ledger/i18n/locales/en-US.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/yoroi-extension/ledger/components/connect/operation/send/SendTxHintBlock.js b/packages/yoroi-extension/ledger/components/connect/operation/send/SendTxHintBlock.js index 6ff66a95a8..1a0aed003e 100644 --- a/packages/yoroi-extension/ledger/components/connect/operation/send/SendTxHintBlock.js +++ b/packages/yoroi-extension/ledger/components/connect/operation/send/SendTxHintBlock.js @@ -179,11 +179,11 @@ const message = defineMessages({ }, confirmAssetFingerprint: { id: 'hint.sendTx.confirmAssetFingerprint', - defaultMessage: '!!!Confirm the asset fingerprint by pressintg both buttons', + defaultMessage: '!!!Confirm the asset fingerprint by pressing both buttons', }, confirmAssetAmount: { id: 'hint.sendTx.confirmAssetAmount', - defaultMessage: '!!!Confirm the token amount by pressintg both buttons', + defaultMessage: '!!!Confirm the token amount by pressing both buttons', }, }); diff --git a/packages/yoroi-extension/ledger/i18n/locales/en-US.json b/packages/yoroi-extension/ledger/i18n/locales/en-US.json index c97346164b..26ccdcaf32 100644 --- a/packages/yoroi-extension/ledger/i18n/locales/en-US.json +++ b/packages/yoroi-extension/ledger/i18n/locales/en-US.json @@ -42,8 +42,8 @@ "hint.sendTx.confirmAddress": "Confirm the receiver's address by pressing both buttons.", "hint.nanoX.sendTx.confirmAddress": "Confirm the receiver's address by pressing the right button to scroll through the entire address. Then press both buttons.", "hint.pointer": "Make sure the pointer shown on your Ledger is the same as the one shown below, then press both buttons.", - "hint.sendTx.confirmAssetAmount": "Confirm the token amount by pressintg both buttons", - "hint.sendTx.confirmAssetFingerprint": "Confirm the asset fingerprint by pressintg both buttons", + "hint.sendTx.confirmAssetAmount": "Confirm the token amount by pressing both buttons", + "hint.sendTx.confirmAssetFingerprint": "Confirm the asset fingerprint by pressing both buttons", "hint.sendTx.confirmFee": "Confirm Transaction Fee by pressing both buttons.", "hint.sendTx.confirmTx": "Confirm the transaction by pressing the right button.", "hint.sendTx.ttl": "Confirm the time-to-live by pressing both buttons.", From 8c486ab4c699b384df5ee16625720f6db07c4da7 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Mon, 26 Sep 2022 16:05:30 +0200 Subject: [PATCH 087/199] Apply scopped modern theme class to the revamp --- .../profile/terms-of-use/TermsOfUseText.scss | 2 +- .../categories/ExternalStorageSettings.scss | 2 +- .../settings/categories/SupportSettings.scss | 2 +- .../transfer/HardwareDisclaimer.scss | 2 +- .../app/components/transfer/SuccessPage.scss | 4 +-- .../transfer/TransferSummaryPage.scss | 2 +- .../app/components/uri/URIGenerateDialog.scss | 2 +- .../app/components/uri/URIVerifyDialog.scss | 2 +- .../components/wallet/WalletCreateDialog.scss | 30 ++++++++++--------- .../app/components/wallet/WalletReceive.scss | 10 ++++--- .../wallet/WalletRestoreDialog.scss | 2 +- .../wallet/WalletRestoreVerifyDialog.scss | 8 +++-- .../app/components/wallet/add/MainCards.scss | 2 +- .../add/paper-wallets/CreatePaperDialog.scss | 2 +- .../wallet/add/paper-wallets/LoadingGif.scss | 2 +- .../add/paper-wallets/UserPasswordDialog.scss | 2 +- .../wallet/backup-recovery/MnemonicWord.scss | 2 +- .../WalletRecoveryInstructions.scss | 2 +- .../WalletRecoveryPhraseEntryDialog.scss | 3 +- .../WalletRecoveryPhraseMnemonic.scss | 2 +- .../wallet/hwConnect/common/CheckDialog.scss | 2 +- .../hwConnect/common/DialogCommonStyle.scss | 2 +- .../wallet/memos/EditMemoDialog.scss | 6 ++-- .../wallet/send/DelegationSendForm.scss | 20 ++++++------- .../send/WalletSendConfirmationDialog.scss | 2 +- 25 files changed, 62 insertions(+), 55 deletions(-) diff --git a/packages/yoroi-extension/app/components/profile/terms-of-use/TermsOfUseText.scss b/packages/yoroi-extension/app/components/profile/terms-of-use/TermsOfUseText.scss index 56ada29ae3..8d0940c521 100644 --- a/packages/yoroi-extension/app/components/profile/terms-of-use/TermsOfUseText.scss +++ b/packages/yoroi-extension/app/components/profile/terms-of-use/TermsOfUseText.scss @@ -41,7 +41,7 @@ color: var(--yoroi-terms-of-use-text-color); } -:global(.YoroiModern) .terms { +:global(.YoroiModern) .terms, :global(.YoroiRevamp) { h1, h2 { font-size: 16px; } diff --git a/packages/yoroi-extension/app/components/settings/categories/ExternalStorageSettings.scss b/packages/yoroi-extension/app/components/settings/categories/ExternalStorageSettings.scss index bb5af347a4..fa4be31a03 100644 --- a/packages/yoroi-extension/app/components/settings/categories/ExternalStorageSettings.scss +++ b/packages/yoroi-extension/app/components/settings/categories/ExternalStorageSettings.scss @@ -46,7 +46,7 @@ } } -:global(.YoroiModern) .component { +:global(.YoroiModern) .component, :global(.YoroiRevamp) { h1 { font-size: 18px; } diff --git a/packages/yoroi-extension/app/components/settings/categories/SupportSettings.scss b/packages/yoroi-extension/app/components/settings/categories/SupportSettings.scss index bb5af347a4..fa4be31a03 100644 --- a/packages/yoroi-extension/app/components/settings/categories/SupportSettings.scss +++ b/packages/yoroi-extension/app/components/settings/categories/SupportSettings.scss @@ -46,7 +46,7 @@ } } -:global(.YoroiModern) .component { +:global(.YoroiModern) .component, :global(.YoroiRevamp) { h1 { font-size: 18px; } diff --git a/packages/yoroi-extension/app/components/transfer/HardwareDisclaimer.scss b/packages/yoroi-extension/app/components/transfer/HardwareDisclaimer.scss index 6c002adcd4..cc15c56ddf 100644 --- a/packages/yoroi-extension/app/components/transfer/HardwareDisclaimer.scss +++ b/packages/yoroi-extension/app/components/transfer/HardwareDisclaimer.scss @@ -35,7 +35,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .headerIcon { content: url(../../assets/images/attention-modern.inline.svg); } diff --git a/packages/yoroi-extension/app/components/transfer/SuccessPage.scss b/packages/yoroi-extension/app/components/transfer/SuccessPage.scss index 87dcb1e6a4..0c0a365baf 100644 --- a/packages/yoroi-extension/app/components/transfer/SuccessPage.scss +++ b/packages/yoroi-extension/app/components/transfer/SuccessPage.scss @@ -29,7 +29,7 @@ } } -:global(.YoroiModern):not(:global(.YoroiShelley)), :global(.YoroiRevamp) .component { +:global(.YoroiModern):not(:global(.YoroiShelley)) .component, :global(.YoroiRevamp):not(:global(.YoroiShelley)) .component { .successImg { background-image: url('../../assets/images/transfer-success.inline.svg'); background-size: auto; @@ -39,7 +39,7 @@ margin: auto; } } -:global(.YoroiModern):global(.YoroiShelley) .component { +:global(.YoroiModern):global(.YoroiShelley) .component, :global(.YoroiRevamp):global(.YoroiShelley) .component { .successImg { background-image: url('../../assets/images/transfer-success-shelley.inline.svg'); background-size: auto; diff --git a/packages/yoroi-extension/app/components/transfer/TransferSummaryPage.scss b/packages/yoroi-extension/app/components/transfer/TransferSummaryPage.scss index 75c19ab6c9..e7f5e434d8 100644 --- a/packages/yoroi-extension/app/components/transfer/TransferSummaryPage.scss +++ b/packages/yoroi-extension/app/components/transfer/TransferSummaryPage.scss @@ -128,7 +128,7 @@ } } -:global(.YoroiModern) .body { +:global(.YoroiModern) .body, :global(.YoroiRevamp) .body { .addressLabel { font-size: 16px; text-transform: capitalize; diff --git a/packages/yoroi-extension/app/components/uri/URIGenerateDialog.scss b/packages/yoroi-extension/app/components/uri/URIGenerateDialog.scss index c58fcc7f5f..ac083b752c 100644 --- a/packages/yoroi-extension/app/components/uri/URIGenerateDialog.scss +++ b/packages/yoroi-extension/app/components/uri/URIGenerateDialog.scss @@ -18,7 +18,7 @@ } } -:global(.YoroiModern) .component { +:global(.YoroiModern) .component, :global(.YoroiRevamp) .component { :global { .Dialog_title { margin-bottom: 38px; diff --git a/packages/yoroi-extension/app/components/uri/URIVerifyDialog.scss b/packages/yoroi-extension/app/components/uri/URIVerifyDialog.scss index 04e32ae6bd..80956d946b 100644 --- a/packages/yoroi-extension/app/components/uri/URIVerifyDialog.scss +++ b/packages/yoroi-extension/app/components/uri/URIVerifyDialog.scss @@ -43,7 +43,7 @@ } } -:global(.YoroiModern) .component { +:global(.YoroiModern) .component, :global(.YoroiRevamp) .component { min-width: var(--yoroi-comp-dialog-min-width-lg); max-width: var(--yoroi-comp-dialog-min-width-lg); } diff --git a/packages/yoroi-extension/app/components/wallet/WalletCreateDialog.scss b/packages/yoroi-extension/app/components/wallet/WalletCreateDialog.scss index 01afd1ba3a..d11c995588 100644 --- a/packages/yoroi-extension/app/components/wallet/WalletCreateDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/WalletCreateDialog.scss @@ -36,20 +36,22 @@ } } -:global(.YoroiModern) .component { - .walletPassword { - .walletPasswordFields { - &.show { - max-height: 600px; - } - - & > div { - width: 100%; +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + .walletPassword { + .walletPasswordFields { + &.show { + max-height: 600px; + } + + & > div { + width: 100%; + } } } + + :global(.Dialog_actions) { + margin-top: 10px; + } } - - :global(.Dialog_actions) { - margin-top: 10px; - } -} +} diff --git a/packages/yoroi-extension/app/components/wallet/WalletReceive.scss b/packages/yoroi-extension/app/components/wallet/WalletReceive.scss index 2ec7648682..e0f574602a 100644 --- a/packages/yoroi-extension/app/components/wallet/WalletReceive.scss +++ b/packages/yoroi-extension/app/components/wallet/WalletReceive.scss @@ -150,10 +150,12 @@ } } -:global(.YoroiModern) .component { - .generatedAddresses { - .walletAddress { - padding: 12px 0; +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + .generatedAddresses { + .walletAddress { + padding: 12px 0; + } } } } diff --git a/packages/yoroi-extension/app/components/wallet/WalletRestoreDialog.scss b/packages/yoroi-extension/app/components/wallet/WalletRestoreDialog.scss index d7661ad25b..62fb061ccb 100644 --- a/packages/yoroi-extension/app/components/wallet/WalletRestoreDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/WalletRestoreDialog.scss @@ -60,7 +60,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .component { .walletName { margin-bottom: 0; diff --git a/packages/yoroi-extension/app/components/wallet/WalletRestoreVerifyDialog.scss b/packages/yoroi-extension/app/components/wallet/WalletRestoreVerifyDialog.scss index b45e2abc5c..b33291dafc 100644 --- a/packages/yoroi-extension/app/components/wallet/WalletRestoreVerifyDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/WalletRestoreVerifyDialog.scss @@ -53,8 +53,10 @@ text-align: center; } -:global(.YoroiModern) .dialog { +:global(.YoroiModern), :global(.YoroiRevamp) { // Refer: https://github.com/Emurgo/yoroi-frontend/pull/1052 - min-width: 740px; - max-width: 740px; + .dialog { + min-width: 740px; + max-width: 740px; + } } diff --git a/packages/yoroi-extension/app/components/wallet/add/MainCards.scss b/packages/yoroi-extension/app/components/wallet/add/MainCards.scss index c1042a90c0..9cdcf985f9 100644 --- a/packages/yoroi-extension/app/components/wallet/add/MainCards.scss +++ b/packages/yoroi-extension/app/components/wallet/add/MainCards.scss @@ -47,7 +47,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .heroCardsItemBg { &.bgConnectHW { background-image: url(../../../assets/images/add-wallet/connect-hw-modern.inline.svg); diff --git a/packages/yoroi-extension/app/components/wallet/add/paper-wallets/CreatePaperDialog.scss b/packages/yoroi-extension/app/components/wallet/add/paper-wallets/CreatePaperDialog.scss index 45a8641f29..51eab01205 100644 --- a/packages/yoroi-extension/app/components/wallet/add/paper-wallets/CreatePaperDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/add/paper-wallets/CreatePaperDialog.scss @@ -55,7 +55,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .component { .downloadIcon { background-image: url('../../../../assets/images/paper-wallet/download-certificate-modern.inline.svg'); diff --git a/packages/yoroi-extension/app/components/wallet/add/paper-wallets/LoadingGif.scss b/packages/yoroi-extension/app/components/wallet/add/paper-wallets/LoadingGif.scss index 72a7b50e15..516e543e4b 100644 --- a/packages/yoroi-extension/app/components/wallet/add/paper-wallets/LoadingGif.scss +++ b/packages/yoroi-extension/app/components/wallet/add/paper-wallets/LoadingGif.scss @@ -22,7 +22,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .component { background-image: url("../../../../assets/images/paper-wallet/create-paper-wallet-loader-modern.gif"); } diff --git a/packages/yoroi-extension/app/components/wallet/add/paper-wallets/UserPasswordDialog.scss b/packages/yoroi-extension/app/components/wallet/add/paper-wallets/UserPasswordDialog.scss index fd33bc0118..8082196922 100644 --- a/packages/yoroi-extension/app/components/wallet/add/paper-wallets/UserPasswordDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/add/paper-wallets/UserPasswordDialog.scss @@ -22,7 +22,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .dialog { :global(.Dialog_actions) { margin-top: 10px; diff --git a/packages/yoroi-extension/app/components/wallet/backup-recovery/MnemonicWord.scss b/packages/yoroi-extension/app/components/wallet/backup-recovery/MnemonicWord.scss index cfa6e4afdc..e0df132e5d 100644 --- a/packages/yoroi-extension/app/components/wallet/backup-recovery/MnemonicWord.scss +++ b/packages/yoroi-extension/app/components/wallet/backup-recovery/MnemonicWord.scss @@ -32,7 +32,7 @@ } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .component { font-size: 14px; margin: 5px 10px 5px 0; diff --git a/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryInstructions.scss b/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryInstructions.scss index 9434c88900..1f75a24708 100644 --- a/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryInstructions.scss +++ b/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryInstructions.scss @@ -16,7 +16,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .component { font-weight: 400; font-size: 14px; diff --git a/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.scss b/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.scss index 10f047fa93..1b5fc480e1 100644 --- a/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.scss @@ -2,7 +2,6 @@ .component { - .checkbox { margin-top: 30px; @@ -57,7 +56,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .component { text-align: left; } diff --git a/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryPhraseMnemonic.scss b/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryPhraseMnemonic.scss index 517e9b69c1..e2ac42a55a 100644 --- a/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryPhraseMnemonic.scss +++ b/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletRecoveryPhraseMnemonic.scss @@ -37,7 +37,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .component { border-radius: 8px; text-align: left; diff --git a/packages/yoroi-extension/app/components/wallet/hwConnect/common/CheckDialog.scss b/packages/yoroi-extension/app/components/wallet/hwConnect/common/CheckDialog.scss index c166511ea3..62a130bb2a 100644 --- a/packages/yoroi-extension/app/components/wallet/hwConnect/common/CheckDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/hwConnect/common/CheckDialog.scss @@ -57,7 +57,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .component { .prerequisiteBlock { margin-top: 24px; diff --git a/packages/yoroi-extension/app/components/wallet/hwConnect/common/DialogCommonStyle.scss b/packages/yoroi-extension/app/components/wallet/hwConnect/common/DialogCommonStyle.scss index 103fc49d0e..a9a2051bbf 100644 --- a/packages/yoroi-extension/app/components/wallet/hwConnect/common/DialogCommonStyle.scss +++ b/packages/yoroi-extension/app/components/wallet/hwConnect/common/DialogCommonStyle.scss @@ -62,7 +62,7 @@ $opacityCommon: 0.7; } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .headerBlock { font-size: 12px; line-height: 18px; diff --git a/packages/yoroi-extension/app/components/wallet/memos/EditMemoDialog.scss b/packages/yoroi-extension/app/components/wallet/memos/EditMemoDialog.scss index ac92fcd007..04b57a236a 100644 --- a/packages/yoroi-extension/app/components/wallet/memos/EditMemoDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/memos/EditMemoDialog.scss @@ -2,6 +2,8 @@ padding-bottom: 0 !important; } -:global(.YoroiModern) .component { - min-width: var(--yoroi-comp-dialog-min-width-md); +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + min-width: var(--yoroi-comp-dialog-min-width-md); + } } diff --git a/packages/yoroi-extension/app/components/wallet/send/DelegationSendForm.scss b/packages/yoroi-extension/app/components/wallet/send/DelegationSendForm.scss index 104a9495bb..fe37a9866d 100644 --- a/packages/yoroi-extension/app/components/wallet/send/DelegationSendForm.scss +++ b/packages/yoroi-extension/app/components/wallet/send/DelegationSendForm.scss @@ -36,14 +36,14 @@ } } -:global(.YoroiModern) .component { - padding: 30px; - - .poolIdInput { - letter-spacing: 0; - } - - .nextButton { - margin: 20px auto 0; - } +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + padding: 30px; + .poolIdInput { + letter-spacing: 0; + } + .nextButton { + margin: 20px auto 0; + } + } } diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss b/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss index 59d27bc84d..cd12f78114 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss @@ -137,7 +137,7 @@ } } -:global(.YoroiModern) .dialog { +:global(.YoroiModern) .dialog, :global(.YoroiRevamp) { min-width: var(--yoroi-comp-dialog-min-width-lg); max-width: var(--yoroi-comp-dialog-min-width-lg); font-weight: 400; From 3fad1708746521e4cc76041f59e7b01ef900e7ad Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 26 Sep 2022 19:21:16 +0800 Subject: [PATCH 088/199] seperate updating utxos from updating txs --- packages/yoroi-extension/app/api/ada/index.js | 6 + .../storage/bridge/tests/multiwallet.test.js | 21 +++ .../lib/storage/bridge/tests/shelley.test.js | 11 ++ .../storage/bridge/tests/simpleTxs.test.js | 36 +++++ .../lib/storage/bridge/tests/status.test.js | 37 ++++- .../ada/lib/storage/bridge/tests/utxo.test.js | 11 ++ .../lib/storage/bridge/updateTransactions.js | 138 +++++++++++------- 7 files changed, 208 insertions(+), 52 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/index.js b/packages/yoroi-extension/app/api/ada/index.js index bfc8f9d9f5..7eb8c96eb2 100644 --- a/packages/yoroi-extension/app/api/ada/index.js +++ b/packages/yoroi-extension/app/api/ada/index.js @@ -21,6 +21,7 @@ import { getPendingTransactions, removeAllTransactions, updateTransactions, + updateUtxos, } from './lib/storage/bridge/updateTransactions'; import { addrContainsAccountKey, @@ -674,6 +675,11 @@ export default class AdaApi { const { skip = 0, limit } = request; try { if (!request.isLocalRequest) { + await updateUtxos( + request.publicDeriver.getDb(), + request.publicDeriver, + request.checkAddressesInUse, + ); await updateTransactions( request.publicDeriver.getDb(), request.publicDeriver, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js index 48358dd565..8d253f9d47 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/multiwallet.test.js @@ -43,6 +43,7 @@ import { } from '../../models/PublicDeriver/traits'; import { + updateUtxos, updateTransactions, removeAllTransactions, } from '../updateTransactions'; @@ -416,6 +417,11 @@ async function syncingSimpleTransaction( // update balance for publicDeriver1 { + await updateUtxos( + db, + withUtxos1, + checkAddressesInUse, + ); await updateTransactions( db, withUtxos1, @@ -456,6 +462,11 @@ async function syncingSimpleTransaction( } // now sync and make sure it updated + await updateUtxos( + db, + withUtxos2, + checkAddressesInUse, + ); await updateTransactions( db, withUtxos2, @@ -483,6 +494,11 @@ async function syncingSimpleTransaction( // check rollback on wallet 2 { + await updateUtxos( + db, + withUtxos2, + checkAddressesInUse, + ); await updateTransactions( db, withUtxos2, @@ -528,6 +544,11 @@ async function syncingSimpleTransaction( txHistory.push(removedTx); { // now sync and make sure it updated + await updateUtxos( + db, + withUtxos2, + checkAddressesInUse, + ); await updateTransactions( db, withUtxos2, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js index d01d190d02..6073266061 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/shelley.test.js @@ -40,6 +40,7 @@ import { } from '../../models/PublicDeriver/traits'; import { + updateUtxos, updateTransactions, } from '../updateTransactions'; import { @@ -243,6 +244,11 @@ async function syncingSimpleTransaction( } { + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -339,6 +345,11 @@ async function syncingSimpleTransaction( { txHistory.push(nextRegularSpend(purposeForTest)); + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js index 2ed893ae92..8dc6164911 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/simpleTxs.test.js @@ -41,6 +41,7 @@ import { } from '../../models/PublicDeriver/traits'; import { + updateUtxos, updateTransactions, } from '../updateTransactions'; import { @@ -335,6 +336,11 @@ async function syncingSimpleTransaction( // test Public Deriver functionality { + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -431,6 +437,11 @@ async function syncingSimpleTransaction( { const dbDump1 = (await db.export()).tables; + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -560,6 +571,11 @@ async function syncingSimpleTransaction( { txHistory.push(nextRegularSpend(purposeForTest)); + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -601,6 +617,11 @@ async function syncingSimpleTransaction( { txHistory.push(...twoTxsRegularSpend(purposeForTest)); + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -637,6 +658,11 @@ async function syncingSimpleTransaction( txHistory.pop(); txHistory.pop(); + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -678,6 +704,11 @@ async function syncingSimpleTransaction( { txHistory.push(...twoTxsRegularSpend(purposeForTest)); + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -774,6 +805,11 @@ async function utxoCreatedAndUsed( // add tx so that we both created and used a utxo in the same sync txHistory.push(nextRegularSpend(purposeForTest)); + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/status.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/status.test.js index 7388301f0e..1111a10939 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/status.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/status.test.js @@ -43,7 +43,7 @@ import { } from '../../models/PublicDeriver/traits'; import { - updateTransactions, getAllTransactions + updateUtxos, updateTransactions, getAllTransactions } from '../updateTransactions'; import { TransactionType } from '../../database/primitives/tables'; import UtxoApi from '../../../state-fetch/utxoApi'; @@ -338,6 +338,11 @@ async function baseTest( // single pending tx { + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -385,6 +390,11 @@ async function baseTest( { networkTransactions.push(otherSpend(purposeForTest)); + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -512,6 +522,11 @@ async function baseTest( }; networkTransactions.push(newTx); + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -694,6 +709,11 @@ async function baseTest( // need to add a pointless tx to advance the bestblock on the server networkTransactions.push(pointlessTx(purposeForTest)); + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -865,6 +885,11 @@ async function baseTest( networkTransactions.pop(); networkTransactions.pop(); + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -1030,6 +1055,11 @@ async function pendingDropped( } // add the pending tx to our wallet + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -1044,6 +1074,11 @@ async function pendingDropped( networkTransactions.pop(); // resync so pending becomes failed + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js index 48eb3f76fa..27322d3a6a 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/tests/utxo.test.js @@ -39,6 +39,7 @@ import { } from '../../models/PublicDeriver/traits'; import { + updateUtxos, updateTransactions, } from '../updateTransactions'; import { @@ -294,6 +295,11 @@ async function syncingSimpleTransaction( } { + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, @@ -406,6 +412,11 @@ async function syncingSimpleTransaction( { txHistory.push(nextRegularSpend(purposeForTest)); + await updateUtxos( + db, + basePubDeriver, + checkAddressesInUse, + ); await updateTransactions( db, basePubDeriver, diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js index 8f5ac274fe..675eeaa5da 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/updateTransactions.js @@ -904,6 +904,89 @@ export async function removeAllTransactions( ); } +export async function updateUtxos( + db: lf$Database, + publicDeriver: IPublicDeriver, + checkAddressesInUse: FilterFunc, +): Promise { + const withLevels = asHasLevels(publicDeriver); + const derivationTables = withLevels == null + ? new Map() + : withLevels.getParent().getDerivationTables(); + + const scanAddrTables = Object.freeze({ + GetKeyForPublicDeriver, + GetAddress, + GetPathWithSpecific, + GetUtxoTxOutputsWithTx, + ModifyAddress, + GetPublicDeriver, + AddDerivationTree, + ModifyDisplayCutoff, + GetDerivationsByPath, + GetDerivationSpecific, + GetKeyDerivation, + }); + + // sync our address set with remote to make sure txs are identified as ours + await raii( + db, + [ + ...Object.keys(scanAddrTables).map(key => scanAddrTables[key]) + .flatMap(table => getAllSchemaTables(db, table)), + ...mapToTables(db, derivationTables), + ], + async dbTx => { + const canScan = asScanAddresses(publicDeriver); + if (canScan != null) { + await canScan.rawScanAddresses( + dbTx, + { + GetKeyForPublicDeriver: scanAddrTables.GetKeyForPublicDeriver, + GetAddress: scanAddrTables.GetAddress, + GetPathWithSpecific: scanAddrTables.GetPathWithSpecific, + GetUtxoTxOutputsWithTx: scanAddrTables.GetUtxoTxOutputsWithTx, + ModifyAddress: scanAddrTables.ModifyAddress, + GetPublicDeriver: scanAddrTables.GetPublicDeriver, + AddDerivationTree: scanAddrTables.AddDerivationTree, + ModifyDisplayCutoff: scanAddrTables.ModifyDisplayCutoff, + GetDerivationsByPath: scanAddrTables.GetDerivationsByPath, + GetDerivationSpecific: scanAddrTables.GetDerivationSpecific, + GetKeyDerivation: scanAddrTables.GetKeyDerivation, + }, + // TODO: race condition because we don't pass in best block here + { checkAddressesInUse }, + derivationTables, + ); + } + } + ); + + const getAddrTables = Object.freeze({ + GetPathWithSpecific, + GetAddress, + GetDerivationSpecific, + }); + + await raii( + db, + [ + ...Object.keys(UtxoStorageApi.depsTables).map(key => UtxoStorageApi.depsTables[key]) + .flatMap(table => getAllSchemaTables(db, table)), + ...Object.keys(getAddrTables).map(key => getAddrTables[key]) + .flatMap(table => getAllSchemaTables(db, table)), + ], + async dbTx => { + await rawUpdateUtxos( + db, dbTx, + publicDeriver, + getAddrTables, + derivationTables, + ); + } + ); +} + export async function updateTransactions( db: lf$Database, publicDeriver: IPublicDeriver, @@ -918,10 +1001,6 @@ export async function updateTransactions( ? new Map() : withLevels.getParent().getDerivationTables(); let lastSyncInfo = undefined; - const updateUtxoTables = Object - .keys(UtxoStorageApi.depsTables) - .map(key => UtxoStorageApi.depsTables[key]) - .flatMap(table => getAllSchemaTables(db, table)); try { const updateDepTables = Object.freeze({ @@ -965,7 +1044,6 @@ export async function updateTransactions( [ ...updateTables, ...mapToTables(db, derivationTables), - ...updateUtxoTables, ], async dbTx => { lastSyncInfo = await updateDepTables.GetLastSyncForPublicDeriver.forId( @@ -977,27 +1055,17 @@ export async function updateTransactions( GetLastSyncForPublicDeriver, // eslint-disable-line no-unused-vars, no-shadow ...remainingDeps } = updateDepTables; - - await rawUpdateTransactions( db, dbTx, remainingDeps, publicDeriver, lastSyncInfo, - checkAddressesInUse, getTransactionsHistoryForAddresses, getBestBlock, derivationTables, getTokenInfo, getMultiAssetMetadata ); - - await updateUtxos( - db, dbTx, - publicDeriver, - remainingDeps, - derivationTables, - ); } ); } catch (e) { @@ -1034,7 +1102,6 @@ export async function updateTransactions( [ ...rollbackTables, ...mapToTables(db, derivationTables), - ...updateUtxoTables, ], async dbTx => { const newLastSyncInfo = await rollbackDepTables.GetLastSyncForPublicDeriver.forId( @@ -1057,13 +1124,6 @@ export async function updateTransactions( }, derivationTables ); - - await updateUtxos( - db, dbTx, - publicDeriver, - rollbackDepTables, - derivationTables, - ); } ); } @@ -1259,7 +1319,6 @@ async function rawUpdateTransactions( |}, publicDeriver: IPublicDeriver<>, lastSyncInfo: $ReadOnly, - checkAddressesInUse: FilterFunc, getTransactionsHistoryForAddresses: HistoryFunc, getBestBlock: BestBlockFunc, derivationTables: Map, @@ -1288,29 +1347,7 @@ async function rawUpdateTransactions( if (bestBlock.hash != null) { const untilBlock = bestBlock.hash; - // 2) sync our address set with remote to make sure txs are identified as ours - const canScan = asScanAddresses(publicDeriver); - if (canScan != null) { - await canScan.rawScanAddresses( - dbTx, - { - GetKeyForPublicDeriver: deps.GetKeyForPublicDeriver, - GetAddress: deps.GetAddress, - GetPathWithSpecific: deps.GetPathWithSpecific, - GetUtxoTxOutputsWithTx: deps.GetUtxoTxOutputsWithTx, - ModifyAddress: deps.ModifyAddress, - GetPublicDeriver: deps.GetPublicDeriver, - AddDerivationTree: deps.AddDerivationTree, - ModifyDisplayCutoff: deps.ModifyDisplayCutoff, - GetDerivationsByPath: deps.GetDerivationsByPath, - GetDerivationSpecific: deps.GetDerivationSpecific, - GetKeyDerivation: deps.GetKeyDerivation, - }, - // TODO: race condition because we don't pass in best block here - { checkAddressesInUse }, - derivationTables, - ); - } + // 2) address syncing has been done by scanUtxos so no need to do it here again // 3) get new txs from fetcher @@ -2849,16 +2886,15 @@ async function certificateToDb( return result; } -async function updateUtxos( +async function rawUpdateUtxos( db: lf$Database, dbTx: lf$Transaction, publicDeriver: IPublicDeriver<>, - deps: { + deps: {| GetPathWithSpecific: Class, GetAddress: Class, GetDerivationSpecific: Class, - ... - }, + |}, derivationTables: Map, ): Promise { const addresses = await rawGetAddressRowsForWallet( From bcfd51eb875e77aaf49e3424cf263564a1c340e9 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 27 Sep 2022 14:21:30 +0300 Subject: [PATCH 089/199] Required updates --- .github/workflows/tests.yml | 1 + .../features/mock-chain/TestWallets.js | 8 ++++- .../yoroi-extension/features/smoke.feature | 20 ++++++++----- .../features/step_definitions/common-steps.js | 7 ++++- .../step_definitions/transactions-steps.js | 30 +++++++++++++++---- 5 files changed, 51 insertions(+), 15 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e999dd7374..22f4f511f7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -327,6 +327,7 @@ jobs: env: FIRST_SMOKE_TEST_WALLET: ${{ secrets.FIRST_SMOKE_TEST_WALLET }} SECOND_SMOKE_TEST_WALLET: ${{ secrets.SECOND_SMOKE_TEST_WALLET }} + SECOND_SMOKE_TEST_WALLET_FF: ${{ secrets.SECOND_SMOKE_TEST_WALLET_FF }} run: xvfb-run -a -e /dev/stdout -s "-screen 0 1920x1080x24" npm run test:run:e2e:smoke:${{ matrix.browser }} - name: Archive tests screenshots and logs diff --git a/packages/yoroi-extension/features/mock-chain/TestWallets.js b/packages/yoroi-extension/features/mock-chain/TestWallets.js index 8199f340f3..2be92e61b6 100644 --- a/packages/yoroi-extension/features/mock-chain/TestWallets.js +++ b/packages/yoroi-extension/features/mock-chain/TestWallets.js @@ -56,7 +56,8 @@ export type WalletNames = 'ergo-token-wallet' | 'cardano-token-wallet' | 'First-Smoke-Test-Wallet' | - 'Second-Smoke-Test-Wallet'; + 'Second-Smoke-Test-Wallet' | + 'Second-Smoke-Test-Wallet-FF'; // eslint-disable-next-line prefer-object-spread export const testWallets: { [key: WalletNames]: RestorationInput, ... } = Object.assign( @@ -179,4 +180,9 @@ export const testWallets: { [key: WalletNames]: RestorationInput, ... } = Object mnemonic: getMnemonicFromEnv('SECOND_SMOKE_TEST_WALLET'), plate: 'XZHD-1651', }), + createWallet({ + name: ('Second-Smoke-Test-Wallet-FF': WalletNames), + mnemonic: getMnemonicFromEnv('SECOND_SMOKE_TEST_WALLET_FF'), + plate: 'CJBE-8896' + }), ); diff --git a/packages/yoroi-extension/features/smoke.feature b/packages/yoroi-extension/features/smoke.feature index 88cc98272b..babf8494fd 100644 --- a/packages/yoroi-extension/features/smoke.feature +++ b/packages/yoroi-extension/features/smoke.feature @@ -35,8 +35,9 @@ Feature: Smoke tests And I have a wallet with funds Then I go to the send transaction screen And I fill the form: - | address | amount | - | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | 1 | + | address | amount | browser | + | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | 1 | chrome | + | addr1qxtrynld5ry55hrdznrdu7yzzjk5hjcna9grs3gegwznxv3ll380dw9nf8u3fry0xlxl4endeur3esvcnxewycdn3epqt4sv08 | 1 | firefox | And I click on the next button in the wallet send form And I see send money confirmation dialog And I enter the wallet password: @@ -57,8 +58,9 @@ Feature: Smoke tests And I open the token selection dropdown And I select token "yoroi" And I fill the form: - | address | amount | - | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | 5 | + | address | amount | browser | + | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | 4 | chrome | + | addr1qxtrynld5ry55hrdznrdu7yzzjk5hjcna9grs3gegwznxv3ll380dw9nf8u3fry0xlxl4endeur3esvcnxewycdn3epqt4sv08 | 4 | firefox | And I click on the next button in the wallet send form And I see send money confirmation dialog And I enter the wallet password: @@ -79,8 +81,9 @@ Feature: Smoke tests And I open the token selection dropdown And I select token "" And I fill the form: - | address | amount | - | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | 15 | + | address | amount | browser | + | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | 7 | chrome | + | addr1qxtrynld5ry55hrdznrdu7yzzjk5hjcna9grs3gegwznxv3ll380dw9nf8u3fry0xlxl4endeur3esvcnxewycdn3epqt4sv08 | 7 | firefox | And I click on the next button in the wallet send form And I see send money confirmation dialog And I enter the wallet password: @@ -107,8 +110,9 @@ Feature: Smoke tests And I open the token selection dropdown And I select token "ADA" And I fill the address of the form: - | address | - | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | + | address | browser | + | addr1qx2dzfu535t6n9nlmh4y8l5mmjvvw7qk3vuser0rdsq04vc0hkzu65nj2s7rcluetdmcxm030cxcuwcn2fq7l0l6pexqsd4d95 | chrome | + | addr1qxtrynld5ry55hrdznrdu7yzzjk5hjcna9grs3gegwznxv3ll380dw9nf8u3fry0xlxl4endeur3esvcnxewycdn3epqt4sv08 | firefox | And I open the amount dropdown and select send all And I click on the next button in the wallet send form And I see send money confirmation dialog diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index fabb026194..90767de9d5 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -394,7 +394,12 @@ Given(/^There is an Ergo wallet stored named ([^"]*)$/, async function (walletNa Given(/^There is a Shelley wallet stored named ([^"]*)$/, async function (walletName: WalletNames) { this.webDriverLogger.info(`Step: There is a Shelley wallet stored named ${walletName}`); - await restoreWallet(this, 'shelley', walletName); + const browserName = await this.getBrowser(); + if (walletName === 'Second-Smoke-Test-Wallet' && browserName === 'firefox') { + await restoreWallet(this, 'shelley', walletName + '-FF'); + } else { + await restoreWallet(this, 'shelley', walletName); + } }); Given(/^There is a Byron wallet stored named ([^"]*)$/, async function (walletName: WalletNames) { diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index 907f3de0e1..af096630eb 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -20,6 +20,18 @@ import { } from '../pages/walletSendPage'; import { halfSecond, oneMinute } from '../support/helpers/common-constants'; +const filterInputByBrowser = async (customWorld: any, inputData: any): Promise => { + const browserName = await customWorld.getBrowser(); + const rows = inputData.hashes(); + let fields = rows.filter(row => row.browser === browserName)[0]; + + if (!fields){ + fields = rows[0]; + } + + return fields; +}; + Given(/^I have a wallet with funds$/, async function () { await this.waitUntilContainsText( { locator: '.NavWalletDetails_amount', method: 'css' }, @@ -43,14 +55,22 @@ When(/^I select the asset "(.+)" on the form$/, async function (assetName) { }); When(/^I fill the form:$/, async function (table) { - const fields = table.hashes()[0]; - await this.input({ locator: "input[name='receiver']", method: 'css' }, fields.address); - await this.input({ locator: "input[name='amount']", method: 'css' }, fields.amount); + const fields = await filterInputByBrowser(this, table); + const receiverInput = { locator: "input[name='receiver']", method: 'css' }; + const amountInput = { locator: "input[name='amount']", method: 'css' }; + + await this.waitForElement(receiverInput); + await this.waitForElement(amountInput); + await this.input(receiverInput, fields.address); + await this.input(amountInput, fields.amount); }); When(/^I fill the address of the form:$/, async function (table) { - const fields = table.hashes()[0]; - await this.input({ locator: "input[name='receiver']", method: 'css' }, fields.address); + const fields = await filterInputByBrowser(this, table); + const receiverInput = { locator: "input[name='receiver']", method: 'css' }; + + await this.waitForElement(receiverInput); + await this.input(receiverInput, fields.address); }); Given(/^The expected transaction is "([^"]*)"$/, base64Tx => { From 1118c25fdc4c7d2e0e56eeecfac422c6e7344cbf Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 27 Sep 2022 14:30:29 +0300 Subject: [PATCH 090/199] flow fix --- .../yoroi-extension/features/step_definitions/common-steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 90767de9d5..9905a945b2 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -396,7 +396,7 @@ Given(/^There is a Shelley wallet stored named ([^"]*)$/, async function (wallet this.webDriverLogger.info(`Step: There is a Shelley wallet stored named ${walletName}`); const browserName = await this.getBrowser(); if (walletName === 'Second-Smoke-Test-Wallet' && browserName === 'firefox') { - await restoreWallet(this, 'shelley', walletName + '-FF'); + await restoreWallet(this, 'shelley', 'Second-Smoke-Test-Wallet-FF'); } else { await restoreWallet(this, 'shelley', walletName); } From c56195b49f44f69bbb69d9b38fc9851d5873fb72 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 27 Sep 2022 14:08:01 +0200 Subject: [PATCH 091/199] Add .YoroiRevamp classes to every global scope --- .../wallet/receive/VerifyAddressDialog.scss | 8 ++++--- .../send/WalletSendConfirmationDialog.scss | 2 +- .../settings/ChangeWalletPasswordDialog.scss | 2 +- .../staking/DelegationSuccessDialog.scss | 16 +++++++------ .../wallet/staking/DelegationTxDialog.scss | 16 +++++++------ .../dashboard/LessThanExpectedDialog.scss | 8 ++++--- .../staking/dashboard/PoolWarningDialog.scss | 8 ++++--- .../staking/dashboard/UndelegateDialog.scss | 16 +++++++------ .../wallet/voting/VotingRegTxDialog.scss | 16 +++++++------ .../warning-dialogs/BaseWarningDialog.scss | 6 +++-- .../app/components/widgets/BorderedBox.scss | 16 +++++++------ .../widgets/DangerousActionDialog.scss | 2 +- .../app/components/widgets/ErrorBlock.scss | 10 ++++---- .../app/components/widgets/ProgressSteps.scss | 23 +++++++++---------- .../app/components/widgets/WarningBox.scss | 2 +- .../widgets/forms/InlineEditingInput.scss | 16 +++++++------ .../widgets/forms/ReadOnlyInput.scss | 16 +++++++------ .../widgets/options/OptionBlock.scss | 2 +- 18 files changed, 104 insertions(+), 81 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/receive/VerifyAddressDialog.scss b/packages/yoroi-extension/app/components/wallet/receive/VerifyAddressDialog.scss index 4b35395ebf..8be3422a0b 100644 --- a/packages/yoroi-extension/app/components/wallet/receive/VerifyAddressDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/receive/VerifyAddressDialog.scss @@ -26,7 +26,9 @@ line-height: 22px; } -:global(.YoroiModern) .component { - min-width: var(--yoroi-comp-dialog-min-width-lg); - max-width: var(--yoroi-comp-dialog-min-width-lg); +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + min-width: var(--yoroi-comp-dialog-min-width-lg); + max-width: var(--yoroi-comp-dialog-min-width-lg); + } } diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss b/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss index cd12f78114..59d27bc84d 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendConfirmationDialog.scss @@ -137,7 +137,7 @@ } } -:global(.YoroiModern) .dialog, :global(.YoroiRevamp) { +:global(.YoroiModern) .dialog { min-width: var(--yoroi-comp-dialog-min-width-lg); max-width: var(--yoroi-comp-dialog-min-width-lg); font-weight: 400; diff --git a/packages/yoroi-extension/app/components/wallet/settings/ChangeWalletPasswordDialog.scss b/packages/yoroi-extension/app/components/wallet/settings/ChangeWalletPasswordDialog.scss index 5c9cf72888..a7633f28af 100644 --- a/packages/yoroi-extension/app/components/wallet/settings/ChangeWalletPasswordDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/settings/ChangeWalletPasswordDialog.scss @@ -16,7 +16,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .dialog { :global(.Dialog_actions) { margin-top: 10px; diff --git a/packages/yoroi-extension/app/components/wallet/staking/DelegationSuccessDialog.scss b/packages/yoroi-extension/app/components/wallet/staking/DelegationSuccessDialog.scss index f097397180..5e9bec0048 100644 --- a/packages/yoroi-extension/app/components/wallet/staking/DelegationSuccessDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/staking/DelegationSuccessDialog.scss @@ -8,12 +8,14 @@ } } -:global(.YoroiModern) .dialog { - min-width: var(--yoroi-comp-dialog-min-width-lg); - max-width: var(--yoroi-comp-dialog-min-width-lg); - font-weight: 400; - - .walletPassword { - margin-top: 0; +:global(.YoroiModern), :global(.YoroiRevamp) { + .dialog { + min-width: var(--yoroi-comp-dialog-min-width-lg); + max-width: var(--yoroi-comp-dialog-min-width-lg); + font-weight: 400; + + .walletPassword { + margin-top: 0; + } } } diff --git a/packages/yoroi-extension/app/components/wallet/staking/DelegationTxDialog.scss b/packages/yoroi-extension/app/components/wallet/staking/DelegationTxDialog.scss index ab17afdb09..55a1efb0f2 100644 --- a/packages/yoroi-extension/app/components/wallet/staking/DelegationTxDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/staking/DelegationTxDialog.scss @@ -74,12 +74,14 @@ } } -:global(.YoroiModern) .dialog { - min-width: var(--yoroi-comp-dialog-min-width-lg); - max-width: var(--yoroi-comp-dialog-min-width-lg); - font-weight: 400; - - .walletPassword { - margin-top: 0; +:global(.YoroiModern), :global(.YoroiRevamp) { + .dialog { + min-width: var(--yoroi-comp-dialog-min-width-lg); + max-width: var(--yoroi-comp-dialog-min-width-lg); + font-weight: 400; + + .walletPassword { + margin-top: 0; + } } } diff --git a/packages/yoroi-extension/app/components/wallet/staking/dashboard/LessThanExpectedDialog.scss b/packages/yoroi-extension/app/components/wallet/staking/dashboard/LessThanExpectedDialog.scss index 97fb29286b..a12e07e5b2 100644 --- a/packages/yoroi-extension/app/components/wallet/staking/dashboard/LessThanExpectedDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/staking/dashboard/LessThanExpectedDialog.scss @@ -1,8 +1,10 @@ @import '../../../../themes/mixins/unordered-list'; -:global(.YoroiModern) .component { - min-width: var(--yoroi-comp-dialog-min-width-lg); - max-width: var(--yoroi-comp-dialog-min-width-lg); +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + min-width: var(--yoroi-comp-dialog-min-width-lg); + max-width: var(--yoroi-comp-dialog-min-width-lg); + } } .component { diff --git a/packages/yoroi-extension/app/components/wallet/staking/dashboard/PoolWarningDialog.scss b/packages/yoroi-extension/app/components/wallet/staking/dashboard/PoolWarningDialog.scss index 5913a2a84a..b114f5f2cf 100644 --- a/packages/yoroi-extension/app/components/wallet/staking/dashboard/PoolWarningDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/staking/dashboard/PoolWarningDialog.scss @@ -1,8 +1,10 @@ @import '../../../../themes/mixins/unordered-list'; -:global(.YoroiModern) .component { - min-width: var(--yoroi-comp-dialog-min-width-md); -} +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + min-width: var(--yoroi-comp-dialog-min-width-md); + } +} .component { font-weight: 400; diff --git a/packages/yoroi-extension/app/components/wallet/staking/dashboard/UndelegateDialog.scss b/packages/yoroi-extension/app/components/wallet/staking/dashboard/UndelegateDialog.scss index b609c3667a..f5c6397e7d 100644 --- a/packages/yoroi-extension/app/components/wallet/staking/dashboard/UndelegateDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/staking/dashboard/UndelegateDialog.scss @@ -41,12 +41,14 @@ } } -:global(.YoroiModern) .dialog { - min-width: var(--yoroi-comp-dialog-min-width-lg); - max-width: var(--yoroi-comp-dialog-min-width-lg); - font-weight: 400; - - .walletPassword { - margin-top: 0; +:global(.YoroiModern), :global(.YoroiRevamp) { + .dialog { + min-width: var(--yoroi-comp-dialog-min-width-lg); + max-width: var(--yoroi-comp-dialog-min-width-lg); + font-weight: 400; + + .walletPassword { + margin-top: 0; + } } } diff --git a/packages/yoroi-extension/app/components/wallet/voting/VotingRegTxDialog.scss b/packages/yoroi-extension/app/components/wallet/voting/VotingRegTxDialog.scss index eaaf3a2f19..9f8e3e5e7d 100644 --- a/packages/yoroi-extension/app/components/wallet/voting/VotingRegTxDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/voting/VotingRegTxDialog.scss @@ -45,12 +45,14 @@ } } -:global(.YoroiModern) .dialog { - min-width: var(--yoroi-comp-dialog-min-width-lg); - max-width: var(--yoroi-comp-dialog-min-width-lg); - font-weight: 400; - - .walletPassword { - margin-top: 0; +:global(.YoroiModern), :global(.YoroiRevamp) { + .dialog { + min-width: var(--yoroi-comp-dialog-min-width-lg); + max-width: var(--yoroi-comp-dialog-min-width-lg); + font-weight: 400; + + .walletPassword { + margin-top: 0; + } } } diff --git a/packages/yoroi-extension/app/components/wallet/warning-dialogs/BaseWarningDialog.scss b/packages/yoroi-extension/app/components/wallet/warning-dialogs/BaseWarningDialog.scss index 03d2a08c67..44ab61acd7 100644 --- a/packages/yoroi-extension/app/components/wallet/warning-dialogs/BaseWarningDialog.scss +++ b/packages/yoroi-extension/app/components/wallet/warning-dialogs/BaseWarningDialog.scss @@ -1,7 +1,9 @@ @import '../../../themes/mixins/underline'; -:global(.YoroiModern) .component { - min-width: var(--yoroi-comp-dialog-min-width-md); +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + min-width: var(--yoroi-comp-dialog-min-width-md); + } } .component { diff --git a/packages/yoroi-extension/app/components/widgets/BorderedBox.scss b/packages/yoroi-extension/app/components/widgets/BorderedBox.scss index 9fb40a6480..7ee5087f92 100644 --- a/packages/yoroi-extension/app/components/widgets/BorderedBox.scss +++ b/packages/yoroi-extension/app/components/widgets/BorderedBox.scss @@ -3,11 +3,13 @@ padding: 20px; } -:global(.YoroiModern) .component { - padding: 30px; - border-radius: 8px; - - label { - background: var(--yoroi-palette-common-white); - } +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + border-radius: 8px; + padding: 30px; + + label { + background: var(--yoroi-palette-common-white); + } + } } diff --git a/packages/yoroi-extension/app/components/widgets/DangerousActionDialog.scss b/packages/yoroi-extension/app/components/widgets/DangerousActionDialog.scss index 349f6b48bc..992ff0c299 100644 --- a/packages/yoroi-extension/app/components/widgets/DangerousActionDialog.scss +++ b/packages/yoroi-extension/app/components/widgets/DangerousActionDialog.scss @@ -27,7 +27,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .dialog { :global(.Dialog_actions) { margin-top: 10px; diff --git a/packages/yoroi-extension/app/components/widgets/ErrorBlock.scss b/packages/yoroi-extension/app/components/widgets/ErrorBlock.scss index 8a2bb1ac06..abcc3084f4 100644 --- a/packages/yoroi-extension/app/components/widgets/ErrorBlock.scss +++ b/packages/yoroi-extension/app/components/widgets/ErrorBlock.scss @@ -10,8 +10,10 @@ } } -:global(.YoroiModern) .component { - span { - font-size: 12px; - } +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + span { + font-size: 12px; + } + } } \ No newline at end of file diff --git a/packages/yoroi-extension/app/components/widgets/ProgressSteps.scss b/packages/yoroi-extension/app/components/widgets/ProgressSteps.scss index 2fc3d88af1..67f6ebd5ee 100644 --- a/packages/yoroi-extension/app/components/widgets/ProgressSteps.scss +++ b/packages/yoroi-extension/app/components/widgets/ProgressSteps.scss @@ -69,17 +69,16 @@ $common-color: var(--yoroi-palette-secondary-300); } } -:global(.YoroiClassic) .component { -} - -:global(.YoroiModern) .component { - .stepTopBar { - height: 4px; - border-radius: 2px; - } - - .stepText { - font-size: 12px; - font-weight: 400; +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + .stepTopBar { + height: 4px; + border-radius: 2px; + } + + .stepText { + font-size: 12px; + font-weight: 400; + } } } diff --git a/packages/yoroi-extension/app/components/widgets/WarningBox.scss b/packages/yoroi-extension/app/components/widgets/WarningBox.scss index d3b6208d3a..5d7fa8ee98 100644 --- a/packages/yoroi-extension/app/components/widgets/WarningBox.scss +++ b/packages/yoroi-extension/app/components/widgets/WarningBox.scss @@ -64,7 +64,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .component { .headerIcon { background-image: url(../../assets/images/attention-modern.inline.svg); diff --git a/packages/yoroi-extension/app/components/widgets/forms/InlineEditingInput.scss b/packages/yoroi-extension/app/components/widgets/forms/InlineEditingInput.scss index 5a5bdd1e35..af0edfb3da 100644 --- a/packages/yoroi-extension/app/components/widgets/forms/InlineEditingInput.scss +++ b/packages/yoroi-extension/app/components/widgets/forms/InlineEditingInput.scss @@ -66,12 +66,14 @@ } } -:global(.YoroiModern) .component { - .disabled { - background-color: transparent; - } - - .button { - bottom: 37px; +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + .disabled { + background-color: transparent; + } + + .button { + bottom: 37px; + } } } diff --git a/packages/yoroi-extension/app/components/widgets/forms/ReadOnlyInput.scss b/packages/yoroi-extension/app/components/widgets/forms/ReadOnlyInput.scss index dddf090c11..9c28f95b49 100644 --- a/packages/yoroi-extension/app/components/widgets/forms/ReadOnlyInput.scss +++ b/packages/yoroi-extension/app/components/widgets/forms/ReadOnlyInput.scss @@ -19,13 +19,15 @@ } } -:global(.YoroiModern) .component { - .disabled { - background-color: transparent; - } - - .button { - bottom: 37px; +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + .disabled { + background-color: transparent; + } + + .button { + bottom: 37px; + } } } diff --git a/packages/yoroi-extension/app/components/widgets/options/OptionBlock.scss b/packages/yoroi-extension/app/components/widgets/options/OptionBlock.scss index 5d694189c3..5191bb3add 100644 --- a/packages/yoroi-extension/app/components/widgets/options/OptionBlock.scss +++ b/packages/yoroi-extension/app/components/widgets/options/OptionBlock.scss @@ -187,7 +187,7 @@ } } -:global(.YoroiModern) { +:global(.YoroiModern), :global(.YoroiRevamp) { .optionBlockListItem { .optionBlockWrapper { .optionSubmitButton { From 3a549161aec6145bf44fab6fd46c289f6813e930 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 27 Sep 2022 15:12:54 +0300 Subject: [PATCH 092/199] Run flow and unit tests in the release check --- .github/workflows/flow-and-lint.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/flow-and-lint.yml b/.github/workflows/flow-and-lint.yml index 4dd35a4cad..47a02492a5 100644 --- a/.github/workflows/flow-and-lint.yml +++ b/.github/workflows/flow-and-lint.yml @@ -6,7 +6,7 @@ on: jobs: flow-and-lint: - if: github.event.review && (github.event.review.state == 'approved' || contains(github.event.review.body, '/check')) + if: github.event.review && (github.event.review.state == 'approved' || contains(github.event.review.body, '/check') || contains(github.event.review.body, '/release-check')) runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 22f4f511f7..4f00e0127f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ on: jobs: Unit_tests: - if: github.event.review && (github.event.review.state == 'approved' || contains(github.event.review.body, '/check')) + if: github.event.review && (github.event.review.state == 'approved' || contains(github.event.review.body, '/check') || contains(github.event.review.body, '/release-check')) runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From 492b7225396835ab16a667efdc7c04a3d4500557 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 27 Sep 2022 14:24:25 +0200 Subject: [PATCH 093/199] Update TopBar style scope --- .../app/components/layout/TopBarLayout.scss | 31 +++++++++------ .../profile/terms-of-use/TermsOfUseText.scss | 36 +++++++++--------- .../categories/ExternalStorageSettings.scss | 8 ++-- .../settings/categories/SupportSettings.scss | 8 ++-- .../transfer/TransferSummaryPage.scss | 38 ++++++++++--------- .../app/components/uri/URIGenerateDialog.scss | 30 ++++++++------- 6 files changed, 84 insertions(+), 67 deletions(-) diff --git a/packages/yoroi-extension/app/components/layout/TopBarLayout.scss b/packages/yoroi-extension/app/components/layout/TopBarLayout.scss index fcf2afdf24..4d8fb1c936 100644 --- a/packages/yoroi-extension/app/components/layout/TopBarLayout.scss +++ b/packages/yoroi-extension/app/components/layout/TopBarLayout.scss @@ -1,18 +1,25 @@ // avoid background image overlapping with language select form @media (min-width: 1100px) and (min-height: 820px), (min-width: 1160px) { - :global(.YoroiModern):global(.YoroiShelley) .languageSelectionBackground { - background-image: url(../../assets/images/select-language-shelley.svg); - background-size: auto; - background-repeat: no-repeat; - background-position-y: bottom; - background-position-x: right; + :global(.YoroiModern):global(.YoroiShelley), + :global(.YoroiRevamp):global(.YoroiShelley) { + .languageSelectionBackground { + background-image: url(../../assets/images/select-language-shelley.svg); + background-size: auto; + background-repeat: no-repeat; + background-position-y: bottom; + background-position-x: right; + } } - :global(.YoroiModern):not(:global(.YoroiShelley)) .languageSelectionBackground { - background-image: url(../../assets/images/select-language.svg); - background-size: auto; - background-repeat: no-repeat; - background-position-y: bottom; - background-position-x: right; + + :global(.YoroiModern):not(:global(.YoroiShelley)), + :global(.YoroiRevamp):not(:global(.YoroiShelley)) { + .languageSelectionBackground { + background-image: url(../../assets/images/select-language.svg); + background-size: auto; + background-repeat: no-repeat; + background-position-y: bottom; + background-position-x: right; + } } } diff --git a/packages/yoroi-extension/app/components/profile/terms-of-use/TermsOfUseText.scss b/packages/yoroi-extension/app/components/profile/terms-of-use/TermsOfUseText.scss index 8d0940c521..20b4c4ee10 100644 --- a/packages/yoroi-extension/app/components/profile/terms-of-use/TermsOfUseText.scss +++ b/packages/yoroi-extension/app/components/profile/terms-of-use/TermsOfUseText.scss @@ -41,22 +41,24 @@ color: var(--yoroi-terms-of-use-text-color); } -:global(.YoroiModern) .terms, :global(.YoroiRevamp) { - h1, h2 { - font-size: 16px; - } - - h1 { - margin-top: 26px; - } - - h2 { - margin-top: 46px; - } - - p, li { - color: var(--yoroi-terms-of-use-text-color); - font-size: 13px; - line-height: 1.54; +:global(.YoroiModern), :global(.YoroiRevamp) { + .terms { + h1, h2 { + font-size: 16px; + } + + h1 { + margin-top: 26px; + } + + h2 { + margin-top: 46px; + } + + p, li { + color: var(--yoroi-terms-of-use-text-color); + font-size: 13px; + line-height: 1.54; + } } } diff --git a/packages/yoroi-extension/app/components/settings/categories/ExternalStorageSettings.scss b/packages/yoroi-extension/app/components/settings/categories/ExternalStorageSettings.scss index fa4be31a03..1d103cdd99 100644 --- a/packages/yoroi-extension/app/components/settings/categories/ExternalStorageSettings.scss +++ b/packages/yoroi-extension/app/components/settings/categories/ExternalStorageSettings.scss @@ -46,8 +46,10 @@ } } -:global(.YoroiModern) .component, :global(.YoroiRevamp) { - h1 { - font-size: 18px; +:global(.YoroiModern) , :global(.YoroiRevamp) { + .component { + h1 { + font-size: 18px; + } } } diff --git a/packages/yoroi-extension/app/components/settings/categories/SupportSettings.scss b/packages/yoroi-extension/app/components/settings/categories/SupportSettings.scss index fa4be31a03..7dbbbafa46 100644 --- a/packages/yoroi-extension/app/components/settings/categories/SupportSettings.scss +++ b/packages/yoroi-extension/app/components/settings/categories/SupportSettings.scss @@ -46,8 +46,10 @@ } } -:global(.YoroiModern) .component, :global(.YoroiRevamp) { - h1 { - font-size: 18px; +:global(.YoroiModern) , :global(.YoroiRevamp) { + .component { + h1 { + font-size: 18px; + } } } diff --git a/packages/yoroi-extension/app/components/transfer/TransferSummaryPage.scss b/packages/yoroi-extension/app/components/transfer/TransferSummaryPage.scss index e7f5e434d8..2048d5491c 100644 --- a/packages/yoroi-extension/app/components/transfer/TransferSummaryPage.scss +++ b/packages/yoroi-extension/app/components/transfer/TransferSummaryPage.scss @@ -128,22 +128,24 @@ } } -:global(.YoroiModern) .body, :global(.YoroiRevamp) .body { - .addressLabel { - font-size: 16px; - text-transform: capitalize; - } - - .amountFeesWrapper { - .amountLabel, - .feesLabel { - font-size: 16px; - text-transform: capitalize; - } - } - - .totalAmountLabel { - font-size: 16px; - text-transform: capitalize; - } +:global(.YoroiModern), :global(.YoroiRevamp) { + .body { + .addressLabel { + font-size: 16px; + text-transform: capitalize; + } + + .amountFeesWrapper { + .amountLabel, + .feesLabel { + font-size: 16px; + text-transform: capitalize; + } + } + + .totalAmountLabel { + font-size: 16px; + text-transform: capitalize; + } + } } diff --git a/packages/yoroi-extension/app/components/uri/URIGenerateDialog.scss b/packages/yoroi-extension/app/components/uri/URIGenerateDialog.scss index ac083b752c..ead591ae27 100644 --- a/packages/yoroi-extension/app/components/uri/URIGenerateDialog.scss +++ b/packages/yoroi-extension/app/components/uri/URIGenerateDialog.scss @@ -18,18 +18,20 @@ } } -:global(.YoroiModern) .component, :global(.YoroiRevamp) .component { - :global { - .Dialog_title { - margin-bottom: 38px; - } - } - - .receiverInput { - letter-spacing: 0; - } - - .amountField { - margin-bottom: 10px; - } +:global(.YoroiModern), :global(.YoroiRevamp) { + .component { + :global { + .Dialog_title { + margin-bottom: 38px; + } + } + + .receiverInput { + letter-spacing: 0; + } + + .amountField { + margin-bottom: 10px; + } + } } From 1fb367625fea092be15fff5a3bb550f1ac7ceaa0 Mon Sep 17 00:00:00 2001 From: yushi Date: Wed, 28 Sep 2022 12:58:29 +0800 Subject: [PATCH 094/199] fix test --- .../app/api/ada/lib/storage/tests/testDb.dump.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/tests/testDb.dump.json b/packages/yoroi-extension/app/api/ada/lib/storage/tests/testDb.dump.json index 35583dc0c9..fba7c87ac0 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/tests/testDb.dump.json +++ b/packages/yoroi-extension/app/api/ada/lib/storage/tests/testDb.dump.json @@ -1,6 +1,6 @@ { "name": "yoroi-schema", - "version": 16, + "version": 17, "tables": { "Network": [ { From 4219d0c0e0948e9334d2398b982b82870a0fe51b Mon Sep 17 00:00:00 2001 From: Patriciu Nista Date: Fri, 30 Sep 2022 15:20:40 +0200 Subject: [PATCH 095/199] Add collateral screen + refactor/remove old code --- .../dapp-connector/add-collateral.inline.svg | 200 ++++++++++++ .../components/signin/AddCollateralPage.js | 295 ++++++++++++++++++ .../components/signin/CardanoSignTxPage.js | 216 +++++-------- .../containers/ConnectContainer.js | 1 + .../containers/SignTxContainer.js | 155 +++++---- .../app/i18n/locales/en-US.json | 2 +- 6 files changed, 655 insertions(+), 214 deletions(-) create mode 100644 packages/yoroi-extension/app/assets/images/dapp-connector/add-collateral.inline.svg create mode 100644 packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js diff --git a/packages/yoroi-extension/app/assets/images/dapp-connector/add-collateral.inline.svg b/packages/yoroi-extension/app/assets/images/dapp-connector/add-collateral.inline.svg new file mode 100644 index 0000000000..85ce4fe451 --- /dev/null +++ b/packages/yoroi-extension/app/assets/images/dapp-connector/add-collateral.inline.svg @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js new file mode 100644 index 0000000000..9e336b34a5 --- /dev/null +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js @@ -0,0 +1,295 @@ +/* eslint-disable no-nested-ternary */ +// @flow +import React, { Component } from 'react'; +import type { Node } from 'react'; +import { intlShape, defineMessages, FormattedHTMLMessage } from 'react-intl'; +import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; +import { Button, Typography, Alert } from '@mui/material'; +import TextField from '../../../components/common/TextField'; +import globalMessages from '../../../i18n/global-messages'; +import { observer } from 'mobx-react'; +import config from '../../../config'; +import vjf from 'mobx-react-form/lib/validators/VJF'; +import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; +import { splitAmount, truncateToken } from '../../../utils/formatters'; +import type { TokenLookupKey, TokenEntry } from '../../../api/common/lib/MultiToken'; +import type { TokenRow } from '../../../api/ada/lib/storage/database/primitives/tables'; +import { + getTokenName, + getTokenIdentifierIfExists, + assetNameFromIdentifier, +} from '../../../stores/stateless/tokenHelpers'; +import BigNumber from 'bignumber.js'; +import ExplorableHashContainer from '../../../containers/widgets/ExplorableHashContainer'; +import { SelectedExplorer } from '../../../domain/SelectedExplorer'; +import type { CardanoConnectorSignRequest, SignSubmissionErrorType } from '../../types'; +import { Box } from '@mui/system'; +import { signTxMessages } from './SignTxPage'; +import { WrongPassphraseError } from '../../../api/ada/lib/cardanoCrypto/cryptoErrors'; +import { LoadingButton } from '@mui/lab'; +import { ReactComponent as AddCollateralIcon } from '../../../assets/images/dapp-connector/add-collateral.inline.svg'; + +type Props = {| + +txData: ?CardanoConnectorSignRequest, + +onCancel: () => void, + +onConfirm: string => Promise, + +getTokenInfo: ($ReadOnly>) => ?$ReadOnly, + +selectedExplorer: SelectedExplorer, + +submissionError: ?SignSubmissionErrorType, +|}; + +const messages = defineMessages({ + incorrectWalletPasswordError: { + id: 'api.errors.IncorrectPasswordError', + defaultMessage: '!!!Incorrect wallet password.', + }, + reorgTitle: { + id: 'connector.signin.reorg.title', + defaultMessage: '!!!Add Collateral', + }, + reorgMessage: { + id: 'connector.signin.reorg.message', + defaultMessage: + '!!!To interact with smart contract in Cardano you should add collateral, which means to make a 0 ADA transaction.

    It is a guarantee that prevent from failing smart contracts and scams. Learn more about collateral.', + }, + sendError: { + id: 'connector.signin.error.sendError', + defaultMessage: '!!!An error occured when sending the transaction.', + }, +}); + +type State = {| + isSubmitting: boolean, +|}; + +@observer +class AddCollateralPage extends Component { + static contextTypes: {| intl: $npm$ReactIntl$IntlFormat |} = { + intl: intlShape.isRequired, + }; + + state: State = { + isSubmitting: false, + }; + + form: ReactToolboxMobxForm = new ReactToolboxMobxForm( + { + fields: { + walletPassword: { + type: 'password', + label: this.context.intl.formatMessage(globalMessages.walletPasswordLabel), + placeholder: this.context.intl.formatMessage( + globalMessages.walletPasswordFieldPlaceholder + ), + value: '', + validators: [ + ({ field }) => { + if (field.value === '') { + return [false, this.context.intl.formatMessage(globalMessages.fieldIsRequired)]; + } + return [true]; + }, + ], + }, + }, + }, + { + options: { + validateOnChange: true, + validateOnBlur: false, + validationDebounceWait: config.forms.FORM_VALIDATION_DEBOUNCE_WAIT, + }, + plugins: { + vjf: vjf(), + }, + } + ); + + submit(): void { + this.form.submit({ + onSuccess: form => { + const { walletPassword } = form.values(); + this.setState({ isSubmitting: true }); + this.props + .onConfirm(walletPassword) + .finally(() => { + this.setState({ isSubmitting: false }); + }) + .catch(error => { + if (error instanceof WrongPassphraseError) { + this.form + .$('walletPassword') + .invalidate(this.context.intl.formatMessage(messages.incorrectWalletPasswordError)); + } else { + throw error; + } + }); + }, + onError: () => {}, + }); + } + + getTicker: ($ReadOnly) => Node = tokenInfo => { + const fingerprint = this.getFingerprint(tokenInfo); + return fingerprint !== undefined ? ( + + {truncateToken(getTokenName(tokenInfo))} + + ) : ( + truncateToken(getTokenName(tokenInfo)) + ); + }; + + getFingerprint: ($ReadOnly) => string | void = tokenInfo => { + if (tokenInfo.Metadata.type === 'Cardano') { + return getTokenIdentifierIfExists(tokenInfo); + } + return undefined; + }; + + _resolveTokenInfo: TokenEntry => ?$ReadOnly = tokenEntry => { + return this.props.getTokenInfo(tokenEntry); + }; + + renderAmountDisplay: ({| + entry: TokenEntry, + |}) => Node = request => { + const tokenInfo = this._resolveTokenInfo(request.entry); + if (!tokenInfo) { + throw new Error('missing token info'); + } + + const numberOfDecimals = tokenInfo ? tokenInfo.Metadata.numberOfDecimals : 0; + const shiftedAmount = request.entry.amount.shiftedBy(-numberOfDecimals); + const ticker = tokenInfo + ? this.getTicker(tokenInfo) + : assetNameFromIdentifier(request.entry.identifier); + + const [beforeDecimalRewards, afterDecimalRewards] = splitAmount( + shiftedAmount, + numberOfDecimals + ); + + // we may need to explicitly add + for positive values + const adjustedBefore = beforeDecimalRewards.startsWith('-') + ? beforeDecimalRewards.replace('-', '') + : beforeDecimalRewards; + + return ( +
    + {adjustedBefore} + {afterDecimalRewards} {ticker} +
    + ); + }; + + render(): Node { + const { txData, onCancel, submissionError } = this.props; + if (!txData) return null; + + const { intl } = this.context; + const { form } = this; + const walletPasswordField = form.$('walletPassword'); + const { isSubmitting } = this.state; + + const txAmountDefaultToken = txData.amount.defaults.defaultIdentifier; + const txAmount = txData.amount.get(txAmountDefaultToken) ?? new BigNumber('0'); + const txFeeAmount = new BigNumber(txData.fee.amount).negated(); + + return ( + + + + {intl.formatMessage(messages.reorgTitle)} + + + + + + + + + + + {intl.formatMessage(signTxMessages.totalAmount)} + + {this.renderAmountDisplay({ + entry: { + identifier: txData.fee.tokenId, + networkId: txData.fee.networkId, + amount: txAmount, + }, + })} + + + + {intl.formatMessage(signTxMessages.transactionFee)} + + {this.renderAmountDisplay({ + entry: { + identifier: txData.fee.tokenId, + networkId: txData.fee.networkId, + amount: txFeeAmount, + }, + })} + + + + + + + {submissionError === 'SEND_TX_ERROR' && ( + {intl.formatMessage(messages.sendError)} + )} + + + + + + + {intl.formatMessage(globalMessages.confirm)} + + + + + ); + } +} + +export default AddCollateralPage; diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js index e4c8e526a4..77ea6750a5 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js @@ -23,7 +23,7 @@ import type { NetworkRow, TokenRow } from '../../../api/ada/lib/storage/database import { getTokenName, getTokenIdentifierIfExists, - assetNameFromIdentifier + assetNameFromIdentifier, } from '../../../stores/stateless/tokenHelpers'; import BigNumber from 'bignumber.js'; import type { UnitOfAccountSettingType } from '../../../types/unitOfAccountType'; @@ -35,10 +35,7 @@ import type { PublicDeriverCache, WhitelistEntry, } from '../../../../chrome/extension/ergo-connector/types'; -import type { - CardanoConnectorSignRequest, - SignSubmissionErrorType -} from '../../types'; +import type { CardanoConnectorSignRequest, SignSubmissionErrorType } from '../../types'; import CardanoUtxoDetails from './CardanoUtxoDetails'; import { Box } from '@mui/system'; import WalletCard from '../connect/WalletCard'; @@ -46,7 +43,7 @@ import SignTxTabs from './SignTxTabs'; import { signTxMessages } from './SignTxPage'; import { WrongPassphraseError } from '../../../api/ada/lib/cardanoCrypto/cryptoErrors'; import { LoadingButton } from '@mui/lab'; -import { ReactComponent as NoDappIcon } from '../../../assets/images/dapp-connector/no-dapp.inline.svg'; +import { ReactComponent as NoDappIcon } from '../../../assets/images/dapp-connector/no-dapp.inline.svg'; type Props = {| +txData: ?CardanoConnectorSignRequest, @@ -64,7 +61,6 @@ type Props = {| +shouldHideBalance: boolean, +selectedWallet: PublicDeriverCache, +connectedWebsite: ?WhitelistEntry, - +isReorg: boolean, +submissionError: ?SignSubmissionErrorType, +signData: ?{| address: string, payload: string |}, |}; @@ -74,14 +70,6 @@ const messages = defineMessages({ id: 'api.errors.IncorrectPasswordError', defaultMessage: '!!!Incorrect wallet password.', }, - reorgTitle: { - id: 'connector.signin.reorg.title', - defaultMessage: '!!!Add Collateral', - }, - reorgMessage: { - id: 'connector.signin.reorg.message', - defaultMessage: '!!!Collateral is a guarantee that prevents smart contract transaction failings and scams. It means you should make a 0 ADA transaction to generate collateral. Learn more about collateral.' - }, sendError: { id: 'connector.signin.error.sendError', defaultMessage: '!!!An error occured when sending the transaction.', @@ -90,7 +78,7 @@ const messages = defineMessages({ type State = {| isSubmitting: boolean, -|} +|}; @observer class SignTxPage extends Component { @@ -100,7 +88,7 @@ class SignTxPage extends Component { state: State = { isSubmitting: false, - } + }; form: ReactToolboxMobxForm = new ReactToolboxMobxForm( { @@ -139,18 +127,21 @@ class SignTxPage extends Component { this.form.submit({ onSuccess: form => { const { walletPassword } = form.values(); - this.setState({ isSubmitting: true }) - this.props.onConfirm(walletPassword).finally(() => { - this.setState({ isSubmitting: false }); - }).catch(error => { - if (error instanceof WrongPassphraseError) { - this.form.$('walletPassword').invalidate( - this.context.intl.formatMessage(messages.incorrectWalletPasswordError) - ) - } else { - throw error; - } - }); + this.setState({ isSubmitting: true }); + this.props + .onConfirm(walletPassword) + .finally(() => { + this.setState({ isSubmitting: false }); + }) + .catch(error => { + if (error instanceof WrongPassphraseError) { + this.form + .$('walletPassword') + .invalidate(this.context.intl.formatMessage(messages.incorrectWalletPasswordError)); + } else { + throw error; + } + }); }, onError: () => {}, }); @@ -206,18 +197,17 @@ class SignTxPage extends Component { } const numberOfDecimals = tokenInfo ? tokenInfo.Metadata.numberOfDecimals : 0; - const shiftedAmount = request.entry.amount.shiftedBy(- numberOfDecimals); - const ticker = tokenInfo ? this.getTicker(tokenInfo) + const shiftedAmount = request.entry.amount.shiftedBy(-numberOfDecimals); + const ticker = tokenInfo + ? this.getTicker(tokenInfo) : assetNameFromIdentifier(request.entry.identifier); let fiatAmountDisplay = null; + // this is a feature flag if (false && this.props.unitOfAccountSetting.enabled === true) { const { currency } = this.props.unitOfAccountSetting; - const price = this.props.getCurrentPrice( - getTokenName(tokenInfo), - currency - ); + const price = this.props.getCurrentPrice(getTokenName(tokenInfo), currency); if (price != null) { const fiatAmount = calculateAndFormatValue(shiftedAmount, price); const [beforeDecimal, afterDecimal] = fiatAmount.split('.'); @@ -230,10 +220,7 @@ class SignTxPage extends Component { fiatAmountDisplay = ( <> {beforeDecimalSigned} - {afterDecimal && ( - .{afterDecimal} - )} - {' '}{currency} + {afterDecimal && .{afterDecimal}} {currency} ); } @@ -258,23 +245,14 @@ class SignTxPage extends Component { if (fiatAmountDisplay) { return ( <> -
    - {fiatAmountDisplay} -
    -
    - {cryptoAmountDisplay} -
    +
    {fiatAmountDisplay}
    +
    {cryptoAmountDisplay}
    ); } - return ( - <> -
    - {cryptoAmountDisplay} -
    - - ); - } + + return
    {cryptoAmountDisplay}
    ; + }; renderRow: ({| kind: string, @@ -350,14 +328,7 @@ class SignTxPage extends Component { const walletPasswordField = form.$('walletPassword'); const { intl } = this.context; - const { - txData, - onCancel, - connectedWebsite, - isReorg, - submissionError, - signData, - } = this.props; + const { txData, onCancel, connectedWebsite, submissionError, signData } = this.props; const { isSubmitting } = this.state; @@ -458,9 +429,7 @@ class SignTxPage extends Component { border="1px solid var(--yoroi-palette-gray-100)" borderRadius="6px" > -
    -              {this.renderPayload(signData.payload)}
    -            
    +
    {this.renderPayload(signData.payload)}
    ); @@ -473,65 +442,61 @@ class SignTxPage extends Component { - {isReorg && ( - <> - - {intl.formatMessage(messages.reorgTitle)} - - - + + + {intl.formatMessage(signTxMessages.connectedTo)} + + + + {faviconUrl != null && faviconUrl !== '' ? ( + {`${url} + ) : ( + + )} + + + {url} - - )} - - {intl.formatMessage(signTxMessages.connectedTo)} - - + - {faviconUrl != null && faviconUrl !== '' ? {`${url} : } + - - {url} - - - - {content} @@ -540,18 +505,7 @@ class SignTxPage extends Component { {...walletPasswordField.bind()} error={walletPasswordField.error} /> - {isReorg && (submissionError === 'SEND_TX_ERROR') && ( - - {intl.formatMessage(messages.sendError)} - - )} - + diff --git a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js index f46bb99eb9..8ce459acb1 100644 --- a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js +++ b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js @@ -234,6 +234,7 @@ export default class ConnectContainer extends Component< stores: { profile: { shouldHideBalance: stores.profile.shouldHideBalance, + // todo: import profile store for stablecoin display }, connector: { connectingMessage: stores.connector.connectingMessage, diff --git a/packages/yoroi-extension/app/ergo-connector/containers/SignTxContainer.js b/packages/yoroi-extension/app/ergo-connector/containers/SignTxContainer.js index c692abfdff..3d8a60d96c 100644 --- a/packages/yoroi-extension/app/ergo-connector/containers/SignTxContainer.js +++ b/packages/yoroi-extension/app/ergo-connector/containers/SignTxContainer.js @@ -26,11 +26,9 @@ import { SelectedExplorer } from '../../domain/SelectedExplorer'; import type { UnitOfAccountSettingType } from '../../types/unitOfAccountType'; import { asGetSigningKey } from '../../api/ada/lib/storage/models/PublicDeriver/traits'; import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index'; -import type { - CardanoConnectorSignRequest, - SignSubmissionErrorType, -} from '../types'; +import type { CardanoConnectorSignRequest, SignSubmissionErrorType } from '../types'; import { Box } from '@mui/material'; +import AddCollateralPage from '../components/signin/AddCollateralPage'; type GeneratedData = typeof SignTxContainer.prototype.generated; @@ -50,7 +48,7 @@ export default class SignTxContainer extends Component< window.addEventListener('unload', this.onUnload); } - onConfirm: (PublicDeriver<> => (string => Promise)) = deriver => async (password) => { + onConfirm: (PublicDeriver<>) => string => Promise = deriver => async password => { const withSigningKey = asGetSigningKey(deriver); if (!withSigningKey) { throw new Error(`[sign tx] no signing key`); @@ -82,11 +80,6 @@ export default class SignTxContainer extends Component< const actions = this.generated.actions; const { uiNotifications } = this.generated.stores; - const tooltipNotification = { - duration: config.wallets.ADDRESS_COPY_TOOLTIP_NOTIFICATION_DURATION, - message: globalMessages.copyTooltipMessage, - }; - const { signingMessage } = this.generated.stores.connector; if (signingMessage == null) return this.renderLoading(); @@ -99,7 +92,49 @@ export default class SignTxContainer extends Component< cacheEntry => selectedWallet.publicDeriver.getPublicDeriverId() === cacheEntry.publicDeriverId ); + const tooltipNotification = { + duration: config.wallets.ADDRESS_COPY_TOOLTIP_NOTIFICATION_DURATION, + message: globalMessages.copyTooltipMessage, + }; + + const handleCopyAddressTooltip = (address, elementId) => { + if (!uiNotifications.isOpen(elementId)) { + runInAction(() => { + this.notificationElementId = elementId; + }); + actions.notifications.open.trigger({ + id: elementId, + duration: tooltipNotification.duration, + message: tooltipNotification.message, + }); + } + }; + + const getAddressToDisplay = addr => + addressToDisplayString(addr, selectedWallet.publicDeriver.getParent().getNetworkInfo()); + + const handleConfirm = password => this.onConfirm(selectedWallet.publicDeriver)(password); + + const notification = + this.notificationElementId == null + ? null + : uiNotifications.getTooltipActiveNotification(this.notificationElementId); + + const signData = + signingMessage.sign.type === 'data' + ? { address: signingMessage.sign.address, payload: signingMessage.sign.payload } + : null; + + const selectedExplorer = + this.generated.stores.explorers.selectedExplorer.get( + selectedWallet.publicDeriver.getParent().getNetworkInfo().NetworkId + ) ?? + (() => { + throw new Error('No explorer for wallet network'); + })(); + let component = null; + // TODO: handle other sign types switch (signingMessage.sign.type) { case 'tx': { @@ -110,103 +145,59 @@ export default class SignTxContainer extends Component< shouldHideBalance={this.generated.stores.profile.shouldHideBalance} connectedWebsite={connectedWebsite} selectedWallet={selectedWallet} - onCopyAddressTooltip={(address, elementId) => { - if (!uiNotifications.isOpen(elementId)) { - runInAction(() => { - this.notificationElementId = elementId; - }); - actions.notifications.open.trigger({ - id: elementId, - duration: tooltipNotification.duration, - message: tooltipNotification.message, - }); - } - }} - notification={ - this.notificationElementId == null - ? null - : uiNotifications.getTooltipActiveNotification(this.notificationElementId) - } + onCopyAddressTooltip={handleCopyAddressTooltip} + notification={notification} tx={signingMessage.sign.tx} txData={txData} getTokenInfo={genLookupOrFail(this.generated.stores.tokenInfoStore.tokenInfo)} defaultToken={selectedWallet.publicDeriver.getParent().getDefaultToken()} network={selectedWallet.publicDeriver.getParent().getNetworkInfo()} - onConfirm={(password) => this.onConfirm(selectedWallet.publicDeriver)(password)} + onConfirm={handleConfirm} onCancel={this.onCancel} - addressToDisplayString={addr => - addressToDisplayString( - addr, - selectedWallet.publicDeriver.getParent().getNetworkInfo() - ) - } + addressToDisplayString={getAddressToDisplay} getCurrentPrice={this.generated.stores.coinPriceStore.getCurrentPrice} - selectedExplorer={ - this.generated.stores.explorers.selectedExplorer.get( - selectedWallet.publicDeriver.getParent().getNetworkInfo().NetworkId - ) ?? - (() => { - throw new Error('No explorer for wallet network'); - })() - } + selectedExplorer={selectedExplorer} unitOfAccountSetting={this.generated.stores.profile.unitOfAccount} /> ); break; } - case 'tx-reorg/cardano': + case 'tx-reorg/cardano': { + const txData = this.generated.stores.connector.adaTransaction; + if (txData == null && signData == null) return this.renderLoading(); + component = ( + + ); + break; + } case 'data': case 'tx/cardano': { const txData = this.generated.stores.connector.adaTransaction; - const signData = signingMessage.sign.type === 'data' - ? { address: signingMessage.sign.address, payload: signingMessage.sign.payload } - : null; if (txData == null && signData == null) return this.renderLoading(); component = ( { - if (!uiNotifications.isOpen(elementId)) { - runInAction(() => { - this.notificationElementId = elementId; - }); - actions.notifications.open.trigger({ - id: elementId, - duration: tooltipNotification.duration, - message: tooltipNotification.message, - }); - } - }} - notification={ - this.notificationElementId == null - ? null - : uiNotifications.getTooltipActiveNotification(this.notificationElementId) - } + onCopyAddressTooltip={handleCopyAddressTooltip} + notification={notification} txData={txData} getTokenInfo={genLookupOrNull(this.generated.stores.tokenInfoStore.tokenInfo)} defaultToken={selectedWallet.publicDeriver.getParent().getDefaultToken()} network={selectedWallet.publicDeriver.getParent().getNetworkInfo()} - onConfirm={(password) => this.onConfirm(selectedWallet.publicDeriver)(password)} + onConfirm={handleConfirm} onCancel={this.onCancel} - addressToDisplayString={addr => - addressToDisplayString( - addr, - selectedWallet.publicDeriver.getParent().getNetworkInfo() - ) - } + addressToDisplayString={getAddressToDisplay} getCurrentPrice={this.generated.stores.coinPriceStore.getCurrentPrice} - selectedExplorer={ - this.generated.stores.explorers.selectedExplorer.get( - selectedWallet.publicDeriver.getParent().getNetworkInfo().NetworkId - ) ?? - (() => { - throw new Error('No explorer for wallet network'); - })() - } + selectedExplorer={selectedExplorer} unitOfAccountSetting={this.generated.stores.profile.unitOfAccount} - isReorg={signingMessage.sign.type === 'tx-reorg/cardano'} submissionError={this.generated.stores.connector.submissionError} signData={signData} /> @@ -217,16 +208,16 @@ export default class SignTxContainer extends Component< component = null; } - return ( + return ( {component} - ) + ); } @computed get generated(): {| diff --git a/packages/yoroi-extension/app/i18n/locales/en-US.json b/packages/yoroi-extension/app/i18n/locales/en-US.json index 0b2f110065..3dcdf4029b 100644 --- a/packages/yoroi-extension/app/i18n/locales/en-US.json +++ b/packages/yoroi-extension/app/i18n/locales/en-US.json @@ -78,7 +78,7 @@ "connector.signin.transactionFee": "Transaction Fee", "connector.signin.error.incorrectPasswordError": "Incorrect wallet password.", "connector.signin.error.sendError": "An error occured when sending the transaction.", - "connector.signin.reorg.message": "Collateral is a guarantee that prevents smart contract transaction failings and scams. It means you should make a 0 ADA transaction to generate collateral. Learn more about collateral.", + "connector.signin.reorg.message": "To interact with smart contract in Cardano you should add collateral, which means to make a 0 ADA transaction.

    It is a guarantee that prevent from failing smart contracts and scams. Learn more about collateral.
    ", "connector.signin.reorg.title": "Add Collateral", "crash.screen.title": "Yoroi crashed", "daedalusTransfer.error.noTransferTxError": "There is no transaction to be sent.", From d2df292823a08435e40aebe38c8072d56cb4100c Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 3 Oct 2022 16:47:55 +0300 Subject: [PATCH 096/199] Fixes after merging --- .../features/pages/restoreWalletPage.js | 54 ++++++------- .../features/pages/walletPage.js | 1 + .../pages/walletTransactionsHistoryPage.js | 71 +++++++++++++++-- .../features/pages/walletTransactionsPage.js | 64 ---------------- .../features/step_definitions/common-steps.js | 34 +++------ .../step_definitions/connector-steps.js | 4 +- .../step_definitions/hardware-steps.js | 18 ----- .../step_definitions/main-ui-steps.js | 2 +- .../step_definitions/transactions-steps.js | 15 ++-- .../step_definitions/tx-history-steps.js | 76 ++----------------- .../features/step_definitions/voting-steps.js | 4 +- .../step_definitions/wallet-paper-steps.js | 2 +- 12 files changed, 119 insertions(+), 226 deletions(-) delete mode 100644 packages/yoroi-extension/features/pages/walletTransactionsPage.js diff --git a/packages/yoroi-extension/features/pages/restoreWalletPage.js b/packages/yoroi-extension/features/pages/restoreWalletPage.js index 1b1545ab05..eb1ec58387 100644 --- a/packages/yoroi-extension/features/pages/restoreWalletPage.js +++ b/packages/yoroi-extension/features/pages/restoreWalletPage.js @@ -4,32 +4,47 @@ import type { LocatorObject } from '../support/webdriver'; import type { RestorationInput } from '../mock-chain/TestWallets'; import { Key } from 'selenium-webdriver'; -export const restoreWalletInputPhraseDialog: LocatorObject = { locator: '.WalletRestoreDialog', method: 'css' }; +export const getWords = (word: string): LocatorObject => { + return { locator: `//span[contains(text(), '${word}')]`, method: 'xpath' }; +}; + +export const enterRecoveryPhrase = async (customWorld: any, phrase: string) => { + const recoveryPhrase = phrase.split(' '); + for (let i = 0; i < recoveryPhrase.length; i++) { + const recoveryPhraseElement = await customWorld.findElement(recoveryPhraseField); + await recoveryPhraseElement.sendKeys(recoveryPhrase[i], Key.RETURN); + if (i === 0) await customWorld.driver.sleep(500); + } +} + +export const inputMnemonicForWallet = async ( + customWorld: any, + restoreInfo: RestorationInput +): Promise => { + await customWorld.input(walletNameInput, restoreInfo.name); + await enterRecoveryPhrase(customWorld, restoreInfo.mnemonic); + await customWorld.input(walletPasswordInput, restoreInfo.password); + await customWorld.input(repeatPasswordInput, restoreInfo.password); + await customWorld.click(confirmConfirmationButton); +} +export const restoreWalletInputPhraseDialog: LocatorObject = { locator: '.WalletRestoreDialog', method: 'css' }; export const errorInvalidRecoveryPhrase: LocatorObject = { locator: '//p[contains(@class, "-error") and contains(@id, "recoveryPhrase")]', method: 'xpath', }; - export const recoveryPhraseField: LocatorObject = { locator: '//input[starts-with(@id, "downshift-") and contains(@id, "-input")]', method: 'xpath', }; - export const proceedRecoveryButton: LocatorObject = { locator: 'primaryButton', method: 'id', }; - export const cleanRecoverInput: LocatorObject = { locator: '.AutocompleteOverridesClassic_autocompleteWrapper input', method: 'css', }; - -export const getWords = (word: string): LocatorObject => { - return { locator: `//span[contains(text(), '${word}')]`, method: 'xpath' }; -}; - export const walletNameInput: LocatorObject = { locator: "input[name='walletName']", method: 'css' }; export const restoreWalletButton: LocatorObject = { locator: '.WalletRestoreDialog .primary', method: 'css' }; export const walletPasswordInput: LocatorObject = { locator: "input[name='walletPassword']", method: 'css' }; @@ -37,23 +52,4 @@ export const repeatPasswordInput: LocatorObject = { locator: "input[name='repeat export const paperPasswordInput: LocatorObject = { locator: "input[name='paperPassword']", method: 'css' }; export const confirmButton: LocatorObject = { locator: '.confirmButton', method: 'css' }; export const confirmConfirmationButton: LocatorObject = { locator: '.WalletRestoreDialog .primary', method: 'css' }; - -export const enterRecoveryPhrase = async (customWorld: any, phrase: string) => { - const recoveryPhrase = phrase.split(' '); - for (let i = 0; i < recoveryPhrase.length; i++) { - const recoveryPhraseElement = await customWorld.findElement(recoveryPhraseField); - await recoveryPhraseElement.sendKeys(recoveryPhrase[i], Key.RETURN); - if (i === 0) await customWorld.driver.sleep(500); - } -} - -export const inputMnemonicForWallet = async ( - customWorld: any, - restoreInfo: RestorationInput -): Promise => { - await customWorld.input(walletNameInput, restoreInfo.name); - await enterRecoveryPhrase(customWorld, restoreInfo.mnemonic); - await customWorld.input(walletPasswordInput, restoreInfo.password); - await customWorld.input(repeatPasswordInput, restoreInfo.password); - await customWorld.click(confirmConfirmationButton); -} \ No newline at end of file +export const restoringDialogPlate: LocatorObject = { locator: 'WalletRestoreVerifyDialog_plateIdSpan', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/pages/walletPage.js b/packages/yoroi-extension/features/pages/walletPage.js index 3a35427511..9a06a1e49d 100644 --- a/packages/yoroi-extension/features/pages/walletPage.js +++ b/packages/yoroi-extension/features/pages/walletPage.js @@ -11,6 +11,7 @@ export const claimTransferTab: LocatorObject = { locator: '.claimTransfer', meth export const votingTab: LocatorObject = { locator: '.voting', method: 'css' }; export const delegationByIdTab: LocatorObject = { locator: '.cardanoStake', method: 'css' }; +export const walletPlate: LocatorObject = { locator: '.NavPlate_plate', method: 'css' }; export const walletNameText: LocatorObject = { locator: '.NavPlate_name', method: 'css' }; export const activeNavTab: LocatorObject = { locator: '.WalletNavButton_active', method: 'css' }; export const dashboardTab: LocatorObject = { locator: '.stakeDashboard ', method: 'css' }; diff --git a/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js b/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js index d5238f9fe3..e017eeee86 100644 --- a/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js +++ b/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js @@ -2,13 +2,7 @@ import type { LocatorObject } from '../support/webdriver'; import { getMethod } from '../support/helpers/helpers'; - -export const walletSummaryBox: LocatorObject = { locator: 'walletSummary_box', method: 'id' }; -export const transactionComponent: LocatorObject = { - locator: '.Transaction_component', - method: 'css', -}; -export const transactionStatus: LocatorObject = { locator: '.Transaction_status', method: 'css' }; +import { By } from 'selenium-webdriver'; export const getTopTx = async (customWorld: any): Promise => { const actualTxsList = await customWorld.getElementsBy(transactionComponent); @@ -21,3 +15,66 @@ export const getTxStatus = async (tx: webdriver$WebElement): Promise => ); return await statusElement.getText(); }; +export const getNotificationMessage = async (customWorld: any, translatedMessage: string) => { + const messageParentElement = await customWorld.driver.findElement( + By.xpath('//div[contains(@role, "tooltip")]') + ); + return await messageParentElement.findElement( + By.xpath(`//span[contains(text(), "${translatedMessage}")]`) + ); +} +export const parseTxInfo = async (addressList: Array): Promise> => { + const addressInfoRow = await addressList.findElements(By.css('.Transaction_addressItem')); + + const result = []; + for (const row of addressInfoRow) { + const rowInfo = await row.findElements(By.xpath('*')); + const rowInfoText = await Promise.all(rowInfo.map(async column => await column.getText())); + result.push(rowInfoText); + } + + return result; +} + +export const walletSummaryBox: LocatorObject = { locator: 'walletSummary_box', method: 'id' }; +export const transactionComponent: LocatorObject = { + locator: '.Transaction_component', + method: 'css', +}; +export const transactionStatus: LocatorObject = { locator: '.Transaction_status', method: 'css' }; +export const walletSummaryComponent: LocatorObject = { + locator: '.WalletSummary_component', + method: 'css', +}; +export const copyToClipboardButton: LocatorObject = { + locator: '.CopyableAddress_copyIconBig', + method: 'css', +}; +export const numberOfTransactions: LocatorObject = { + locator: '.WalletSummary_numberOfTransactions', + method: 'css', +}; +export const noTransactionsComponent: LocatorObject = { + locator: '.WalletNoTransactions_component', + method: 'css', +}; +export const showMoreButton: LocatorObject = { + locator: '.WalletTransactionsList_component .MuiButton-primary', + method: 'css', +}; +export const transactionListElement: LocatorObject = { + locator: '.Transaction_component', + method: 'css', +}; +export const pendingTransactionElement: LocatorObject = { + locator: '.Transaction_pendingLabel', + method: 'css', +}; +export const failedTransactionElement: LocatorObject = { + locator: '.Transaction_failedLabel', + method: 'css', +}; +export const transactionAddressListElement: LocatorObject = { + locator: '.Transaction_addressList', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/pages/walletTransactionsPage.js b/packages/yoroi-extension/features/pages/walletTransactionsPage.js deleted file mode 100644 index d1794d121f..0000000000 --- a/packages/yoroi-extension/features/pages/walletTransactionsPage.js +++ /dev/null @@ -1,64 +0,0 @@ -// @flow - -import type { LocatorObject } from '../support/webdriver'; -import { By } from 'selenium-webdriver'; - -export const getNotificationMessage = async (customWorld: any, translatedMessage: string) => { - const messageParentElement = await customWorld.driver.findElement( - By.xpath('//div[contains(@role, "tooltip")]') - ); - return await messageParentElement.findElement( - By.xpath(`//span[contains(text(), "${translatedMessage}")]`) - ); -} - -export const parseTxInfo = async (addressList: Array): Promise> => { - const addressInfoRow = await addressList.findElements(By.css('.Transaction_addressItem')); - - const result = []; - for (const row of addressInfoRow) { - const rowInfo = await row.findElements(By.xpath('*')); - const rowInfoText = await Promise.all(rowInfo.map(async column => await column.getText())); - result.push(rowInfoText); - } - - return result; -} - -export const walletSummaryBox: LocatorObject = { locator: 'walletSummary_box', method: 'id' }; -export const walletSummaryComponent: LocatorObject = { - locator: '.WalletSummary_component', - method: 'css', -}; -export const copyToClipboardButton: LocatorObject = { - locator: '.CopyableAddress_copyIconBig', - method: 'css', -}; -export const numberOfTransactions: LocatorObject = { - locator: '.WalletSummary_numberOfTransactions', - method: 'css', -}; -export const noTransactionsComponent: LocatorObject = { - locator: '.WalletNoTransactions_component', - method: 'css', -}; -export const showMoreButton: LocatorObject = { - locator: '.WalletTransactionsList_component .MuiButton-primary', - method: 'css', -}; -export const transactionListElement: LocatorObject = { - locator: '.Transaction_component', - method: 'css', -}; -export const pendingTransactionElement: LocatorObject = { - locator: '.Transaction_pendingLabel', - method: 'css', -}; -export const failedTransactionElement: LocatorObject = { - locator: '.Transaction_failedLabel', - method: 'css', -}; -export const transactionAddressListElement: LocatorObject = { - locator: '.Transaction_addressList', - method: 'css', -}; diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index eb84014433..7f64c04734 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -12,7 +12,7 @@ import { } from 'cucumber'; import * as CardanoServer from '../mock-chain/mockCardanoServer'; import * as ErgoServer from '../mock-chain/mockErgoServer'; -import { By, logging } from 'selenium-webdriver'; +import { logging } from 'selenium-webdriver'; import { getLogDate } from '../support/helpers/helpers'; import { testWallets } from '../mock-chain/TestWallets'; import * as ErgoImporter from '../mock-chain/mockErgoImporter'; @@ -28,6 +28,7 @@ import { } from '../support/helpers/common-constants'; import { expect } from 'chai'; import { satisfies } from 'semver'; +// eslint-disable-next-line import/named import { truncateLongName } from '../../app/utils/formatters'; import stableStringify from 'json-stable-stringify'; import type { RestorationInput, WalletNames } from '../mock-chain/TestWallets'; @@ -52,11 +53,8 @@ import { walletRestoreOptionDialog, restoreNormalWallet, walletRestoreDialog, - pickUpCurrencyDialogCardano, walletAddRestoreWalletButton, saveButton, - restoreOptionDialog, - normalWordWalletButton, byronEraButton, createWalletButton, createOptionDialog, @@ -68,6 +66,9 @@ import { inputMnemonicForWallet, walletPasswordInput, repeatPasswordInput, + confirmButton, + restoreWalletButton, + restoringDialogPlate, } from '../pages/restoreWalletPage'; import { backupPrivacyWarningDialog, @@ -83,14 +84,7 @@ import { walletRecoveryPhraseDisplayDialog } from '../pages/createWalletPage'; import * as helpers from '../support/helpers/helpers'; -import { extensionTabName } from '../support/windowManager'; -import { - confirmButton, - repeatPasswordInput, - restoreWalletButton, - walletPasswordInput, -} from '../pages/restoreWalletPage'; -import { walletNameText } from '../pages/walletPage'; +import { walletNameText, walletPlate } from '../pages/walletPage'; import { continueButton, getTosCheckbox, @@ -252,13 +246,11 @@ After(async function (scenario) { export async function getPlates(customWorld: any): Promise { // check plate in confirmation dialog - let plateElements = await customWorld.driver.findElements( - By.css('.WalletRestoreVerifyDialog_plateIdSpan') - ); + let plateElements = await customWorld.findElements(restoringDialogPlate); // this makes this function also work for wallets that already exist if (plateElements.length === 0) { - plateElements = await customWorld.driver.findElements(By.css('.NavPlate_plate')); + plateElements = await customWorld.findElements(walletPlate); } return plateElements; } @@ -357,9 +349,9 @@ async function restoreWallet ( await customWorld.waitForElement(pickUpCurrencyDialog); await customWorld.click(getCurrencyButton('cardano')); - await customWorld.waitForElement(restoreOptionDialog); + await customWorld.waitForElement(walletRestoreDialog); - await customWorld.click(normalWordWalletButton); + await customWorld.click(restoreNormalWallet); if (walletEra === 'shelley') { await customWorld.click(shelleyEraButton); } else if (walletEra === 'byron') { @@ -378,12 +370,6 @@ async function checkWalletPlate( walletName: string, restoreInfo: RestorationInput ): Promise { - await customWorld.input(walletNameInput, restoreInfo.name); - await enterRecoveryPhrase(customWorld, restoreInfo.mnemonic); - await customWorld.input(walletPasswordInput, restoreInfo.password); - await customWorld.input(repeatPasswordInput, restoreInfo.password); - await customWorld.click(restoreWalletButton); - const plateElements = await getPlates(customWorld); const plateText = await plateElements[0].getText(); expect(plateText).to.be.equal(restoreInfo.plate); diff --git a/packages/yoroi-extension/features/step_definitions/connector-steps.js b/packages/yoroi-extension/features/step_definitions/connector-steps.js index 8a229ea2cf..1c5eb9b7a3 100644 --- a/packages/yoroi-extension/features/step_definitions/connector-steps.js +++ b/packages/yoroi-extension/features/step_definitions/connector-steps.js @@ -259,7 +259,7 @@ Then(/^I should see no password errors$/, async function () { }); When(/^I click the back button \(Connector pop-up window\)$/, async function () { - this.webDriverLogger.info(`Step: I click the back button \(Connector pop-up window\)`); + this.webDriverLogger.info(`Step: I click the back button (Connector pop-up window)`); await this.waitForElement(backButton); await this.click(backButton); }); @@ -325,7 +325,7 @@ Then(/^I should see "No Cardano wallets is found" message$/, async function () { }); Then(/^I press the "Create wallet" button \(Connector pop-up window\)$/, async function () { - this.webDriverLogger.info(`Step: I press the "Create wallet" button \(Connector pop-up window\)`); + this.webDriverLogger.info(`Step: I press the "Create wallet" button (Connector pop-up window)`); await this.waitForElement(createWalletBtn); await this.click(createWalletBtn); }); diff --git a/packages/yoroi-extension/features/step_definitions/hardware-steps.js b/packages/yoroi-extension/features/step_definitions/hardware-steps.js index 80a8eec158..cdae21e452 100644 --- a/packages/yoroi-extension/features/step_definitions/hardware-steps.js +++ b/packages/yoroi-extension/features/step_definitions/hardware-steps.js @@ -24,23 +24,17 @@ import { verifyAddressButton, verifyAddressHWButton } from '../pages/walletRecei When(/^I select a Byron-era Ledger device$/, async function () { await this.click(connectHwButton); - await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement(hwOptionsDialog); - await this.click(ledgerWalletButton); await this.click(byronEraButton); }); When(/^I select a Shelley-era Ledger device$/, async function () { await this.click(connectHwButton); - await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement(hwOptionsDialog); - await this.click(ledgerWalletButton); await this.click(shelleyEraButton); }); @@ -48,32 +42,23 @@ When(/^I restore the Ledger device$/, async function () { await this.waitForElement(checkDialog); await this.click(primaryButton); await this.click(primaryButton); - - // between these is where the tab & iframe gets opened - await this.waitForElement(saveDialog); await this.click(primaryButton); }); When(/^I select a Byron-era Trezor device$/, async function () { await this.click(connectHwButton); - await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement(hwOptionsDialog); - await this.click(trezorWalletButton); await this.click(byronEraButton); }); When(/^I select a Shelley-era Trezor device$/, async function () { await this.click(connectHwButton); - await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogCardano); - await this.waitForElement(hwOptionsDialog); - await this.click(trezorWalletButton); await this.click(shelleyEraButton); }); @@ -82,9 +67,6 @@ When(/^I restore the Trezor device$/, async function () { await this.waitForElement(checkDialog); await this.click(primaryButton); await this.click(primaryButton); - - // between these is where the tab & iframe gets opened - await this.waitForElement(saveDialog); await this.input(walletNameInput, testWallets['trezor-wallet'].name); await this.click(primaryButton); diff --git a/packages/yoroi-extension/features/step_definitions/main-ui-steps.js b/packages/yoroi-extension/features/step_definitions/main-ui-steps.js index fabfb76ca3..ca70730c28 100644 --- a/packages/yoroi-extension/features/step_definitions/main-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/main-ui-steps.js @@ -20,7 +20,7 @@ import { copyToClipboardButton, getNotificationMessage, walletSummaryComponent, -} from '../pages/walletTransactionsPage'; +} from '../pages/walletTransactionsHistoryPage'; import { maintenanceBody, serverErrorBanner } from '../pages/mainWindowPage'; import { getRewardValue, getTotalAdaValue } from '../pages/dashboardPage'; diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index 8905e39860..c4b97fa020 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -11,17 +11,19 @@ import { networks, defaultAssets, } from '../../app/api/ada/lib/storage/database/prepackaged/networks'; -import { walletSummaryBox, walletSummaryComponent } from '../pages/walletTransactionsPage'; +import { walletSummaryBox, walletSummaryComponent } from '../pages/walletTransactionsHistoryPage'; import { amountInput, - assetListElement, - assetSelector, disabledSubmitButton, + getTokenLocator, invalidAddressError, nextButton, notEnoughAdaError, receiverInput, + selectAssetDropDown, + selectSendingAmountDropDown, sendAllCheckbox, + sendAllItem, sendConfirmationDialogAddressToText, sendConfirmationDialogAmountText, sendConfirmationDialogError, @@ -37,13 +39,6 @@ import { sendTab } from '../pages/walletPage'; import { walletPasswordInput } from '../pages/restoreWalletPage'; import { delegationTxDialogError } from '../pages/walletDelegationPage'; import { unmangleButton } from '../pages/walletReceivePage'; -import { walletSummaryBox } from '../pages/walletTransactionsHistoryPage'; -import { - getTokenLocator, - selectAssetDropDown, - selectSendingAmountDropDown, - sendAllItem -} from '../pages/walletSendPage'; import { halfSecond, oneMinute } from '../support/helpers/common-constants'; const filterInputByBrowser = async (customWorld: any, inputData: any): Promise => { diff --git a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js index 55c3645c02..7dcf7e5c11 100644 --- a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js +++ b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js @@ -7,17 +7,18 @@ import moment from 'moment'; import i18n from '../support/helpers/i18n-helpers'; import { failedTransactionElement, + getTopTx, + getTxStatus, noTransactionsComponent, numberOfTransactions, parseTxInfo, pendingTransactionElement, - showMoreButton, transactionAddressListElement, - transactionListElement, -} from '../pages/walletTransactionsPage'; + showMoreButton, + transactionAddressListElement, + transactionComponent, +} from '../pages/walletTransactionsHistoryPage'; import { summaryTab } from '../pages/walletPage'; -import { displayInfo } from '../support/helpers/common-constants'; -import { getTopTx, getTxStatus, transactionComponent } from '../pages/walletTransactionsHistoryPage'; -import { txSuccessfulStatuses } from '../support/helpers/common-constants'; +import { displayInfo , txSuccessfulStatuses } from '../support/helpers/common-constants'; function verifyAllTxsFields( txType, @@ -78,7 +79,7 @@ Then( Then(/^I should see no transactions$/, async function () { await this.waitForElement(noTransactionsComponent); const actualTxsList = await this.getElementsBy(transactionComponent); - chai.expect(actualTxsList.length).to.equal(0); + expect(actualTxsList.length).to.equal(0); }); Then(/^I should see ([^"]*) ([^"]*) transactions$/, async function (txsNumber, txExpectedStatus) { @@ -121,19 +122,6 @@ When(/^I expand the top transaction$/, async function () { await topTx.click(); }); -async function parseTxInfo(addressList) { - const addressInfoRow = await addressList.findElements(By.css('.Transaction_addressItem')); - - const result = []; - for (const row of addressInfoRow) { - const rowInfo = await row.findElements(By.xpath('*')); - const rowInfoText = await Promise.all(rowInfo.map(async column => await column.getText())); - result.push(rowInfoText); - } - - return result; -} - Then(/^I verify top transaction content ([^"]*)$/, async function (walletName) { await this.waitForElement(transactionComponent); const actualTxsList = await this.getElementsBy(transactionComponent); @@ -197,54 +185,6 @@ Then( expect(confirmationCount).to.equal(count); }); -const displayInfo = { - 'many-tx-wallet': { - txType: 'ADA intrawallet transaction', - txAmount: '-0.169999', - txTime: '2019-04-21T15:13:33.000Z', - txStatus: 'HIGH', - txFrom: [ - ['Ae2tdPwUPE...VWfitHfUM9', 'BYRON - INTERNAL', '-0.82 ADA'], - ], - txTo: [ - ['Ae2tdPwUPE...iLjTnt34Aj', 'BYRON - EXTERNAL', '+0.000001 ADA'], - ['Ae2tdPwUPE...BA7XbSMhKd', 'BYRON - INTERNAL', '+0.65 ADA'], - ], - txId: '0a073669845fea4ae83cd4418a0b4fd56610097a89601a816b5891f667e3496c', - txConfirmations: 'High. 104 confirmations.', - txFee: '0.169999', - }, - 'simple-pending-wallet': { - txType: 'ADA intrawallet transaction', - txAmount: '-0.999999', - txTime: '2019-04-20T23:14:52.000Z', - txStatus: 'PENDING', - txFrom: [ - ['Ae2tdPwUPE...e1cT2aGdSJ', 'BYRON - EXTERNAL', '-1 ADA'], - ], - txTo: [ - ['Ae2tdPwUPE...sTrQfTxPVX', 'PROCESSING...', '+0.000001 ADA'] - ], - txId: 'fa6f2c82fb511d0cc9c12a540b5fac6e5a9b0f288f2d140f909f981279e16fbe', - txFee: '0.999999', - }, - 'failed-single-tx': { - txType: 'ADA sent', - txAmount: '-0.18', - txTime: '2019-04-20T23:14:51.000Z', - txStatus: 'FAILED', - txFrom: [ - ['Ae2tdPwUPE...gBfkkDNBNv', 'BYRON - EXTERNAL', '-1 ADA'], - ], - txTo: [ - ['Ae2tdPwUPE...xJPmFzi6G2', 'ADDRESS BOOK', '+0.000001 ADA'], - ['Ae2tdPwUPE...bL4UYPN3eU', 'BYRON - INTERNAL', '+0.82 ADA'], - ], - txId: 'fc6a5f086c0810de3048651ddd9075e6e5543bf59cdfe5e0c73bf1ed9dcec1ab', - txFee: '0.179999', - }, -}; - When(/^I go to the tx history screen$/, async function () { await this.click(summaryTab); }); diff --git a/packages/yoroi-extension/features/step_definitions/voting-steps.js b/packages/yoroi-extension/features/step_definitions/voting-steps.js index e8973f6bdb..ab9330bec5 100644 --- a/packages/yoroi-extension/features/step_definitions/voting-steps.js +++ b/packages/yoroi-extension/features/step_definitions/voting-steps.js @@ -88,8 +88,8 @@ Then(/^I see should see pin mismatch error$/, async function () { await this.waitUntilText(confirmPinDialogError, errorMessage); // clear the wrong pin at the end - // we doing backspace 4 times for pin length of 4 - // as .clear() does not update the react component value + // we are doing backspace 4 times for pin length of 4 + // as .clear() does not update the React component value const input = this.driver.findElement(By.name('pin')); for (let i = 1; i <= 4; i++) { input.sendKeys(Key.BACK_SPACE); diff --git a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js index aec3109c65..e6bafba44b 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-paper-steps.js @@ -3,7 +3,7 @@ import { Given, Then } from 'cucumber'; import { expect } from 'chai'; import { truncateAddress } from '../../app/utils/formatters'; -import { enterRecoveryPhrase } from '../support/helpers/helpers'; +import { enterRecoveryPhrase } from '../pages/restoreWalletPage'; import { primaryButton } from '../pages/commonDialogPage'; import { addressElement, From 0238ec6010b97413b158d21249229d0864cafd69 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 3 Oct 2022 17:01:37 +0300 Subject: [PATCH 097/199] Flow fixes --- .../features/pages/commonDialogPage.js | 2 +- .../features/pages/walletReceivePage.js | 16 ++++++++-------- .../features/pages/walletSendPage.js | 2 +- .../pages/walletTransactionsHistoryPage.js | 7 +++++-- .../addresses-generation-steps.js | 8 ++++---- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/yoroi-extension/features/pages/commonDialogPage.js b/packages/yoroi-extension/features/pages/commonDialogPage.js index c14598a576..5edb759edb 100644 --- a/packages/yoroi-extension/features/pages/commonDialogPage.js +++ b/packages/yoroi-extension/features/pages/commonDialogPage.js @@ -15,7 +15,7 @@ export const warningCheckboxElement: LocatorObject = { method: 'css', }; -export const getWarningCheckbox = async (customWorld: Object) => { +export const getWarningCheckbox = async (customWorld: Object): Promise => { const warningCheckboxComponent = await customWorld.findElement(warningCheckboxElement); return await warningCheckboxComponent.findElement(By.xpath('//input[@type="checkbox"]')); } diff --git a/packages/yoroi-extension/features/pages/walletReceivePage.js b/packages/yoroi-extension/features/pages/walletReceivePage.js index 5764614af2..13690e0c7b 100644 --- a/packages/yoroi-extension/features/pages/walletReceivePage.js +++ b/packages/yoroi-extension/features/pages/walletReceivePage.js @@ -26,29 +26,29 @@ export const getSubTabButton = (chain: string, kind: string): LocatorObject => { return { locator: `div.${chain}.${kind}.ReceiveNavButton_wrapper`, method: 'css' }; }; -export const getAllAddressesButton = async (): Promise => { - const translatedText = await i18n.formatMessage(this.driver, { +export const getAllAddressesButton = async (customWorld: any): Promise => { + const translatedText = await i18n.formatMessage(customWorld.driver, { id: 'wallet.receive.navigation.allLabel', }); return getReceiveSubTabButton(translatedText); }; -export const getUnusedAddressesButton = async (): Promise => { - const translatedText = await i18n.formatMessage(this.driver, { +export const getUnusedAddressesButton = async (customWorld: any): Promise => { + const translatedText = await i18n.formatMessage(customWorld.driver, { id: 'wallet.receive.navigation.unusedLabel', }); return getReceiveSubTabButton(translatedText); }; -export const getUsedAddressesButton = async (): Promise => { - const translatedText = await i18n.formatMessage(this.driver, { +export const getUsedAddressesButton = async (customWorld: any): Promise => { + const translatedText = await i18n.formatMessage(customWorld.driver, { id: 'wallet.receive.navigation.usedLabel', }); return getReceiveSubTabButton(translatedText); }; -export const getHasBalanceButton = async (): Promise => { - const translatedText = await i18n.formatMessage(this.driver, { +export const getHasBalanceButton = async (customWorld: any): Promise => { + const translatedText = await i18n.formatMessage(customWorld.driver, { id: 'wallet.receive.navigation.hasBalanceLabel', }); return getReceiveSubTabButton(translatedText); diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js index aaab2a21a9..50e0d93fd5 100644 --- a/packages/yoroi-extension/features/pages/walletSendPage.js +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -20,7 +20,7 @@ export const memoDialogComponent: LocatorObject = { method: 'css', }; export const memoContentText: LocatorObject = { locator: '.memoContent', method: 'css' }; -export const getMemoText = async (customWorld: Object) => { +export const getMemoText = async (customWorld: Object): Promise => { const memoElem = await customWorld.getElementsBy(memoContentText); return await memoElem[0].getText(); }; diff --git a/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js b/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js index e017eeee86..f386f0a190 100644 --- a/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js +++ b/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js @@ -15,7 +15,10 @@ export const getTxStatus = async (tx: webdriver$WebElement): Promise => ); return await statusElement.getText(); }; -export const getNotificationMessage = async (customWorld: any, translatedMessage: string) => { +export const getNotificationMessage = async ( + customWorld: any, + translatedMessage: string +): Promise => { const messageParentElement = await customWorld.driver.findElement( By.xpath('//div[contains(@role, "tooltip")]') ); @@ -23,7 +26,7 @@ export const getNotificationMessage = async (customWorld: any, translatedMessage By.xpath(`//span[contains(text(), "${translatedMessage}")]`) ); } -export const parseTxInfo = async (addressList: Array): Promise> => { +export const parseTxInfo = async (addressList: webdriver$WebElement): Promise> => { const addressInfoRow = await addressList.findElements(By.css('.Transaction_addressItem')); const result = []; diff --git a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js index 5b3fe5eef7..01b8117bb7 100644 --- a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js @@ -49,16 +49,16 @@ When(/^I click on the top-level reward address tab$/, async function () { }); When(/^I click on the All addresses button$/, async function () { - await this.click(await getAllAddressesButton()); + await this.click(await getAllAddressesButton(this)); }); When(/^I click on the Unused addresses button$/, async function () { - await this.click(await getUnusedAddressesButton()); + await this.click(await getUnusedAddressesButton(this)); }); When(/^I click on the Used addresses button$/, async function () { - await this.click(await getUsedAddressesButton()); + await this.click(await getUsedAddressesButton(this)); }); When(/^I click on the HasBalance addresses button$/, async function () { - await this.click(await getHasBalanceButton()); + await this.click(await getHasBalanceButton(this)); }); When(/^I click on the Generate new address button ([0-9]+) times$/, async function (times) { From 53fd70e528b64c014377ae81d116205ca97eb98a Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 3 Oct 2022 19:05:53 +0300 Subject: [PATCH 098/199] Fixes wallet restoration --- .../features/pages/newWalletPages.js | 4 ++-- .../features/pages/restoreWalletPage.js | 5 +++-- .../features/step_definitions/common-steps.js | 19 +++++++++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/yoroi-extension/features/pages/newWalletPages.js b/packages/yoroi-extension/features/pages/newWalletPages.js index f27acdb58b..31470013b3 100644 --- a/packages/yoroi-extension/features/pages/newWalletPages.js +++ b/packages/yoroi-extension/features/pages/newWalletPages.js @@ -7,7 +7,7 @@ export const createWalletButton: LocatorObject = { locator: '.WalletAdd_btnCreateWallet', method: 'css', }; -export const walletAddRestoreWalletButton: LocatorObject = { +export const restoreWalletButton: LocatorObject = { locator: '.WalletAdd_btnRestoreWallet', method: 'css', }; @@ -37,7 +37,7 @@ export const restore24WordWallet: LocatorObject = { method: 'css', }; export const walletRestoreDialog: LocatorObject = { - locator: '.WalletRestoreDialog', + locator: '.WalletRestoreOptionDialog', method: 'css', }; export const getCurrencyButton = (currency: string): LocatorObject => { diff --git a/packages/yoroi-extension/features/pages/restoreWalletPage.js b/packages/yoroi-extension/features/pages/restoreWalletPage.js index eb1ec58387..53f5e7c93c 100644 --- a/packages/yoroi-extension/features/pages/restoreWalletPage.js +++ b/packages/yoroi-extension/features/pages/restoreWalletPage.js @@ -46,10 +46,11 @@ export const cleanRecoverInput: LocatorObject = { method: 'css', }; export const walletNameInput: LocatorObject = { locator: "input[name='walletName']", method: 'css' }; -export const restoreWalletButton: LocatorObject = { locator: '.WalletRestoreDialog .primary', method: 'css' }; +export const confirmRestoreWalletButton: LocatorObject = { locator: '.WalletRestoreDialog .primary', method: 'css' }; export const walletPasswordInput: LocatorObject = { locator: "input[name='walletPassword']", method: 'css' }; export const repeatPasswordInput: LocatorObject = { locator: "input[name='repeatPassword']", method: 'css' }; export const paperPasswordInput: LocatorObject = { locator: "input[name='paperPassword']", method: 'css' }; export const confirmButton: LocatorObject = { locator: '.confirmButton', method: 'css' }; export const confirmConfirmationButton: LocatorObject = { locator: '.WalletRestoreDialog .primary', method: 'css' }; -export const restoringDialogPlate: LocatorObject = { locator: 'WalletRestoreVerifyDialog_plateIdSpan', method: 'css' }; \ No newline at end of file +export const verifyRestoredInfoDialog: LocatorObject = { locator: '.WalletRestoreVerifyDialog_dialog', method: 'css' }; +export const restoringDialogPlate: LocatorObject = { locator: '.WalletRestoreVerifyDialog_plateIdSpan', method: 'css' }; \ No newline at end of file diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 7f64c04734..f2042057ee 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -53,7 +53,7 @@ import { walletRestoreOptionDialog, restoreNormalWallet, walletRestoreDialog, - walletAddRestoreWalletButton, + restoreWalletButton, saveButton, byronEraButton, createWalletButton, @@ -67,8 +67,8 @@ import { walletPasswordInput, repeatPasswordInput, confirmButton, - restoreWalletButton, restoringDialogPlate, + verifyRestoredInfoDialog, } from '../pages/restoreWalletPage'; import { backupPrivacyWarningDialog, @@ -341,28 +341,35 @@ async function restoreWallet ( walletEra: string, walletName: WalletNames ): Promise { + customWorld.webDriverLogger.info(`Step:restoreWallet: Restoring the wallet "${walletName}"`); const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); await customWorld.click(restoreWalletButton); - + customWorld.webDriverLogger.info(`Step:restoreWallet: Clicked restoreWalletButton`); await customWorld.waitForElement(pickUpCurrencyDialog); await customWorld.click(getCurrencyButton('cardano')); - + customWorld.webDriverLogger.info(`Step:restoreWallet: Selected currency "cardano"`); await customWorld.waitForElement(walletRestoreDialog); await customWorld.click(restoreNormalWallet); + customWorld.webDriverLogger.info(`Step:restoreWallet: Selected 15-word wallet`); if (walletEra === 'shelley') { await customWorld.click(shelleyEraButton); + customWorld.webDriverLogger.info(`Step:restoreWallet: Selected era "shelley"`); } else if (walletEra === 'byron') { await customWorld.click(byronEraButton); + customWorld.webDriverLogger.info(`Step:restoreWallet: Selected era "byron"`); } else { throw new Error(`Unknown wallet era: ${walletEra}.`); } await customWorld.waitForElement(restoreWalletInputPhraseDialog); await inputMnemonicForWallet(customWorld, restoreInfo); + customWorld.webDriverLogger.info(`Step:restoreWallet: Mnemonic phrase is entered`); + await customWorld.waitForElement(verifyRestoredInfoDialog); await checkWalletPlate(customWorld, walletName, restoreInfo); + customWorld.webDriverLogger.info(`Step:restoreWallet: Wallet plate is checked`); } async function checkWalletPlate( @@ -396,7 +403,7 @@ Given(/^There is an Ergo wallet stored named ([^"]*)$/, async function (walletNa const restoreInfo = testWallets[walletName]; expect(restoreInfo).to.not.equal(undefined); - await this.click(walletAddRestoreWalletButton); + await this.click(restoreWalletButton); await this.waitForElement(pickUpCurrencyDialog); await this.click(pickUpCurrencyDialogErgo); @@ -463,7 +470,7 @@ Given(/^I have completed the basic setup$/, async function () { await this.click(continueButton); // ToS page await this.waitForElement(termsOfUseComponent); - const checkbox = await getTosCheckbox(); + const checkbox = await getTosCheckbox(this); await checkbox.click(); await this.click(continueButton); // uri prompt page From 18c615a17eb81fdc44f65a3f6816ddf68f642aaf Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 3 Oct 2022 19:28:00 +0300 Subject: [PATCH 099/199] Fixed switching to the revamp version --- .../categories/general-setting/ThemeSettingsBlock.js | 4 +++- packages/yoroi-extension/features/pages/settingsPage.js | 3 ++- .../features/step_definitions/common-steps.js | 6 ++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/yoroi-extension/app/components/settings/categories/general-setting/ThemeSettingsBlock.js b/packages/yoroi-extension/app/components/settings/categories/general-setting/ThemeSettingsBlock.js index d776c3de13..a731289b31 100644 --- a/packages/yoroi-extension/app/components/settings/categories/general-setting/ThemeSettingsBlock.js +++ b/packages/yoroi-extension/app/components/settings/categories/general-setting/ThemeSettingsBlock.js @@ -132,14 +132,16 @@ export default class ThemeSettingsBlock extends Component { value={OLD_THEME} control={} label={intl.formatMessage(messages.currentVersion)} + id="switchToOldVersionButton" sx={{ marginRight: '20px' }} /> } + control={} label={intl.formatMessage(messages.newVersion)} + id="switchToNewVersionButton" />
    diff --git a/packages/yoroi-extension/features/pages/settingsPage.js b/packages/yoroi-extension/features/pages/settingsPage.js index a8718357d1..063d5b6c6a 100644 --- a/packages/yoroi-extension/features/pages/settingsPage.js +++ b/packages/yoroi-extension/features/pages/settingsPage.js @@ -92,7 +92,8 @@ export const secondThemeSelected: LocatorObject = { method: 'css', }; -export const revampThemeButton: LocatorObject = { locator: 'switchToRevampButton', method: 'id' }; +export const oldThemeRadiobutton: LocatorObject = { locator: 'switchToOldVersionButton', method: 'id' }; +export const revampThemeRadiobutton: LocatorObject = { locator: 'switchToNewVersionButton', method: 'id' }; // Change password dialog diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index f2042057ee..7b81070d04 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -95,7 +95,7 @@ import { import { getComplexityLevelButton, goToSettings, - revampThemeButton, + revampThemeRadiobutton, selectSubmenuSettings, settingsLayoutComponent, } from '../pages/settingsPage'; @@ -761,8 +761,10 @@ Then(/^I compare to DB state snapshot excluding sync time$/, async function () { Then(/^Revamp. I switch to revamp version$/, async function () { this.webDriverLogger.info(`Step: Revamp. I switch to revamp version`); await goToSettings(this); + this.webDriverLogger.info(`Step: -----> We are in the Settings`); await selectSubmenuSettings(this, 'general'); - await this.click(revampThemeButton); + this.webDriverLogger.info(`Step: -----> We are in the Settings - General`); + await this.click(revampThemeRadiobutton); }); Then(/^Revamp. I go to the wallet ([^"]*)$/, async function (walletName) { From 74f5779851412741cc6fa35506879100d8072dfc Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 3 Oct 2022 20:33:01 +0300 Subject: [PATCH 100/199] Fixed locator --- packages/yoroi-extension/features/pages/walletReceivePage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/pages/walletReceivePage.js b/packages/yoroi-extension/features/pages/walletReceivePage.js index 13690e0c7b..76d7742078 100644 --- a/packages/yoroi-extension/features/pages/walletReceivePage.js +++ b/packages/yoroi-extension/features/pages/walletReceivePage.js @@ -78,7 +78,7 @@ export const verifyAddressButton: LocatorObject = { method: 'css', }; -export const walletAddressRow: LocatorObject = { locator: 'WalletReceive_walletAddress', method: 'css' }; +export const walletAddressRow: LocatorObject = { locator: '.WalletReceive_walletAddress', method: 'css' }; export const walletAddressLocator = '.WalletReceive_addressHash'; export const verifyAddressHWButton: LocatorObject = { locator: '.VerifyAddressDialog_component .primary', From 05e215447ab76043895759436e93e0e3d46b7920 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 3 Oct 2022 20:52:37 +0300 Subject: [PATCH 101/199] Flow fix --- .../features/step_definitions/wallet-restoration-steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js index 7c6187d1b6..c353fb19db 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-restoration-steps.js @@ -14,7 +14,6 @@ import { paperPasswordInput, recoveryPhraseField, repeatPasswordInput, - restoreWalletButton, walletPasswordInput, } from '../pages/restoreWalletPage'; import { masterKeyInput } from '../pages/walletClaimTransferPage'; @@ -24,6 +23,7 @@ import { pickUpCurrencyDialogCardano, recoveryPhraseDeleteIcon, recoveryPhraseError, + restoreWalletButton, restore24WordWallet, restoreDialogButton, restoreNormalWallet, From cbe7b6444bae5029ca4be2e5f7730909e29c7368 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 3 Oct 2022 22:50:43 +0300 Subject: [PATCH 102/199] Eslint fix --- .../features/step_definitions/installation-procedure-steps.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js index e6fa1d4925..b38493a5a6 100644 --- a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js +++ b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js @@ -9,6 +9,7 @@ Given(/^I am on the "Terms of use" screen$/, async function () { }); When(/^I click on "I agree with the terms of use" checkbox$/, async function () { + this.webDriverLogger.info(`Step: I click on "I agree with the terms of use" checkbox`); const checkbox = await getTosCheckbox(); await checkbox.click(); }); From 95ab09bfcaa9c1ee0f955db1101531d59d876367 Mon Sep 17 00:00:00 2001 From: Patriciu Nista Date: Wed, 5 Oct 2022 10:10:22 +0200 Subject: [PATCH 103/199] Added links --- .../app/ergo-connector/components/signin/AddCollateralPage.js | 4 ++-- packages/yoroi-extension/app/i18n/locales/en-US.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js index 9e336b34a5..c4039ae647 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js @@ -50,7 +50,7 @@ const messages = defineMessages({ reorgMessage: { id: 'connector.signin.reorg.message', defaultMessage: - '!!!To interact with smart contract in Cardano you should add collateral, which means to make a 0 ADA transaction.

    It is a guarantee that prevent from failing smart contracts and scams. Learn more about collateral.
    ', + '!!!To interact with smart contract in Cardano you should add collateral, which means to make a 0 ADA transaction.

    It is a guarantee that prevent from failing smart contracts and scams. Learn more about collateral.
    ', }, sendError: { id: 'connector.signin.error.sendError', @@ -229,7 +229,7 @@ class AddCollateralPage extends Component { gap="16px" > - {intl.formatMessage(signTxMessages.totalAmount)} + {intl.formatMessage(globalMessages.labels.amount)} {this.renderAmountDisplay({ entry: { diff --git a/packages/yoroi-extension/app/i18n/locales/en-US.json b/packages/yoroi-extension/app/i18n/locales/en-US.json index 3dcdf4029b..40c224f3fb 100644 --- a/packages/yoroi-extension/app/i18n/locales/en-US.json +++ b/packages/yoroi-extension/app/i18n/locales/en-US.json @@ -78,7 +78,7 @@ "connector.signin.transactionFee": "Transaction Fee", "connector.signin.error.incorrectPasswordError": "Incorrect wallet password.", "connector.signin.error.sendError": "An error occured when sending the transaction.", - "connector.signin.reorg.message": "To interact with smart contract in Cardano you should add collateral, which means to make a 0 ADA transaction.

    It is a guarantee that prevent from failing smart contracts and scams. Learn more about collateral.
    ", + "connector.signin.reorg.message": "To interact with smart contract in Cardano you should add collateral, which means to make a 0 ADA transaction.

    It is a guarantee that prevent from failing smart contracts and scams. Learn more about collateral.
    ", "connector.signin.reorg.title": "Add Collateral", "crash.screen.title": "Yoroi crashed", "daedalusTransfer.error.noTransferTxError": "There is no transaction to be sent.", From 665503d5cf4f2481f9a43e12f9d8b12d717b46e5 Mon Sep 17 00:00:00 2001 From: Patriciu Nista Date: Wed, 5 Oct 2022 11:20:23 +0200 Subject: [PATCH 104/199] Added fiat display --- .../components/connect/ConnectPage.js | 37 ++++++++---------- .../components/connect/WalletCard.js | 38 +++++++++++++++---- .../containers/ConnectContainer.js | 21 +++++++--- 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js index 01aa7d7457..090c4fdb75 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/ConnectPage.js @@ -2,8 +2,16 @@ // @flow import { Component } from 'react'; import type { Node } from 'react'; -import { intlShape, defineMessages } from 'react-intl'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; +import type { + PublicDeriverCache, + ConnectingMessage, +} from '../../../../chrome/extension/ergo-connector/types'; +import type { TokenLookupKey } from '../../../api/common/lib/MultiToken'; +import type { TokenRow } from '../../../api/ada/lib/storage/database/primitives/tables'; +import type { WalletChecksum } from '@emurgo/cip4-js'; +import type { UnitOfAccountSettingType } from '../../../types/unitOfAccountType'; +import { intlShape, defineMessages } from 'react-intl'; import classNames from 'classnames'; import styles from './ConnectPage.scss'; import { Button, Stack, styled, Typography } from '@mui/material'; @@ -11,16 +19,9 @@ import WalletCard from './WalletCard'; import globalMessages, { connectorMessages } from '../../../i18n/global-messages'; import { observer } from 'mobx-react'; import LoadingSpinner from '../../../components/widgets/LoadingSpinner'; -import type { - PublicDeriverCache, - ConnectingMessage, -} from '../../../../chrome/extension/ergo-connector/types'; import { LoadingWalletStates } from '../../types'; import ProgressBar from '../ProgressBar'; -import type { TokenLookupKey } from '../../../api/common/lib/MultiToken'; -import type { TokenRow } from '../../../api/ada/lib/storage/database/primitives/tables'; import { environment } from '../../../environment'; -import type { WalletChecksum } from '@emurgo/cip4-js'; import { PublicDeriver } from '../../../api/ada/lib/storage/models/PublicDeriver'; import { Box } from '@mui/system'; import TextField from '../../../components/common/TextField'; @@ -99,6 +100,8 @@ type Props = {| +getTokenInfo: ($ReadOnly>) => $ReadOnly, +network: string, +shouldHideBalance: boolean, + +unitOfAccount: UnitOfAccountSettingType, + +getCurrentPrice: (from: string, to: string) => ?string, +onUpdateHideBalance: void => Promise, |}; @@ -226,25 +229,15 @@ class ConnectPage extends Component { const hasWallets = isSuccess && Boolean(publicDerivers.length); const passwordForm = ( - <> +
    - - {intl.formatMessage(messages.connectWalletAuthRequest)} -
    - + - +
    ); return ( @@ -335,6 +328,8 @@ class ConnectPage extends Component { shouldHideBalance={shouldHideBalance} publicDeriver={item} getTokenInfo={this.props.getTokenInfo} + unitOfAccountSetting={this.props.unitOfAccount} + getCurrentPrice={this.props.getCurrentPrice} /> diff --git a/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.js b/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.js index 103f87ad36..8727ae41bb 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.js +++ b/packages/yoroi-extension/app/ergo-connector/components/connect/WalletCard.js @@ -8,19 +8,23 @@ import type { WalletChecksum } from '@emurgo/cip4-js'; import type { PublicDeriverCache } from '../../../../chrome/extension/ergo-connector/types'; import type { TokenLookupKey } from '../../../api/common/lib/MultiToken'; import type { TokenRow } from '../../../api/ada/lib/storage/database/primitives/tables'; +import type { UnitOfAccountSettingType } from '../../../types/unitOfAccountType'; import { assetNameFromIdentifier, getTokenName } from '../../../stores/stateless/tokenHelpers'; import { hiddenAmount } from '../../../utils/strings'; +import { FiatDisplay } from '../../../components/common/AmountDisplay'; type Props = {| +shouldHideBalance: boolean, +publicDeriver: PublicDeriverCache, - +getTokenInfo: $ReadOnly> => ?$ReadOnly, + +getTokenInfo: ($ReadOnly>) => ?$ReadOnly, + +unitOfAccountSetting: UnitOfAccountSettingType, + +getCurrentPrice: (from: string, to: string) => ?string, |}; function constructPlate( plate: WalletChecksum, saturationFactor: number, - divClass: string, + divClass: string ): [string, React$Element<'div'>] { return [ plate.TextPart, @@ -36,7 +40,24 @@ function constructPlate( } export default class WalletCard extends Component { + renderWalletTotal({ ticker, shiftedAmount }: {| ticker: string, shiftedAmount: any |}): ?Node { + const { unitOfAccountSetting, shouldHideBalance, getCurrentPrice } = this.props; + if (unitOfAccountSetting.enabled) { + if (ticker == null) { + throw new Error('unexpected main token type'); + } + const { currency } = unitOfAccountSetting; + if (!currency) { + throw new Error(`unexpected unit of account ${String(currency)}`); + } + const price = getCurrentPrice(ticker, currency); + if (price == null) return null; + const val = shiftedAmount.multipliedBy(price); + + return ; + } + } render(): Node { // eslint-disable-next-line no-unused-vars const [_, iconComponent] = this.props.publicDeriver.checksum @@ -47,7 +68,8 @@ export default class WalletCard extends Component { const tokenInfo = this.props.getTokenInfo(defaultEntry); const numberOfDecimals = tokenInfo ? tokenInfo.Metadata.numberOfDecimals : 0; const shiftedAmount = defaultEntry.amount.shiftedBy(-numberOfDecimals); - const ticker = tokenInfo ? getTokenName(tokenInfo) + const ticker = tokenInfo + ? getTokenName(tokenInfo) : assetNameFromIdentifier(defaultEntry.identifier); const { shouldHideBalance } = this.props; @@ -62,10 +84,12 @@ export default class WalletCard extends Component {
    -

    - {shouldHideBalance ? hiddenAmount : shiftedAmount.toString()}{' '} - {ticker} -

    +
    +

    + {shouldHideBalance ? hiddenAmount : shiftedAmount.toString()} {ticker} +

    +

    {this.renderWalletTotal({ shiftedAmount, ticker })}

    +
    ); } diff --git a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js index cc7cdb9cfd..692cc59f4b 100644 --- a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js +++ b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js @@ -11,10 +11,11 @@ import type { WhitelistEntry, ConnectResponseData, } from '../../../chrome/extension/ergo-connector/types'; -import { LoadingWalletStates } from '../types'; -import { genLookupOrFail } from '../../stores/stateless/tokenHelpers'; +import type { UnitOfAccountSettingType } from '../../types/unitOfAccountType'; import type { TokenInfoMap } from '../../stores/toplevel/TokenInfoStore'; import type { WalletChecksum } from '@emurgo/cip4-js'; +import { LoadingWalletStates } from '../types'; +import { genLookupOrFail } from '../../stores/stateless/tokenHelpers'; import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver'; import { createAuthEntry } from '../api'; @@ -154,8 +155,8 @@ export default class ConnectContainer extends Component< }; hidePasswordForm: void => void = () => { - this.setState({ isAppAuth: false }) - } + this.setState({ isAppAuth: false }); + }; updateHideBalance: void => Promise = async () => { await this.generated.actions.connector.updateHideBalance.trigger(); @@ -184,6 +185,8 @@ export default class ConnectContainer extends Component< network={network} getTokenInfo={genLookupOrFail(this.generated.stores.tokenInfoStore.tokenInfo)} shouldHideBalance={this.generated.stores.profile.shouldHideBalance} + unitOfAccount={this.generated.stores.profile.unitOfAccount} + getCurrentPrice={this.generated.stores.coinPriceStore.getCurrentPrice} onUpdateHideBalance={this.updateHideBalance} /> ); @@ -217,6 +220,10 @@ export default class ConnectContainer extends Component< stores: {| profile: {| shouldHideBalance: boolean, + unitOfAccount: UnitOfAccountSettingType, + |}, + coinPriceStore: {| + getCurrentPrice: (from: string, to: string) => ?string, |}, connector: {| connectingMessage: ?ConnectingMessage, @@ -242,6 +249,10 @@ export default class ConnectContainer extends Component< stores: { profile: { shouldHideBalance: stores.profile.shouldHideBalance, + unitOfAccount: stores.profile.unitOfAccount, + }, + coinPriceStore: { + getCurrentPrice: stores.coinPriceStore.getCurrentPrice, }, connector: { connectingMessage: stores.connector.connectingMessage, @@ -262,7 +273,7 @@ export default class ConnectContainer extends Component< closeWindow: { trigger: actions.connector.closeWindow.trigger }, getConnectorWhitelist: { trigger: actions.connector.getConnectorWhitelist.trigger }, updateConnectorWhitelist: { trigger: actions.connector.updateConnectorWhitelist.trigger }, - updateHideBalance: { trigger: actions.profile.updateHideBalance.trigger } + updateHideBalance: { trigger: actions.profile.updateHideBalance.trigger }, }, }, }); From 47fcabb388dbe656f57e0df53d8aadd18a639a7e Mon Sep 17 00:00:00 2001 From: Patriciu Nista Date: Wed, 5 Oct 2022 11:26:40 +0200 Subject: [PATCH 105/199] Hidden fiat display styles fix --- .../app/components/common/AmountDisplay.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/yoroi-extension/app/components/common/AmountDisplay.js b/packages/yoroi-extension/app/components/common/AmountDisplay.js index d145516917..4f583de294 100644 --- a/packages/yoroi-extension/app/components/common/AmountDisplay.js +++ b/packages/yoroi-extension/app/components/common/AmountDisplay.js @@ -19,7 +19,7 @@ type Props = {| +amount: null | MultiToken, +unitOfAccountSetting: UnitOfAccountSettingType, +getCurrentPrice: (from: string, to: string) => ?string, -|} +|}; export default class AmountDisplay extends Component { static defaultProps: {| showAmount: boolean, showFiat: boolean |} = { showAmount: true, @@ -27,13 +27,7 @@ export default class AmountDisplay extends Component { }; render(): Node { - const { - amount, - shouldHideBalance, - showFiat, - showAmount, - unitOfAccountSetting, - } = this.props + const { amount, shouldHideBalance, showFiat, showAmount, unitOfAccountSetting } = this.props; if (amount == null) { return
    ; } @@ -88,7 +82,7 @@ export default class AmountDisplay extends Component { {balanceDisplay} {truncateToken(getTokenName(tokenInfo))}

    )} - {(showFiat === true && unitOfAccountSetting.enabled) && ( + {showFiat === true && unitOfAccountSetting.enabled && (

    {fiatDisplay} {currency}

    @@ -96,7 +90,7 @@ export default class AmountDisplay extends Component { ); } -}; +} export function FiatDisplay(props: {| shouldHideBalance: boolean, @@ -104,7 +98,11 @@ export function FiatDisplay(props: {| currency: string, |}): Node { if (props.shouldHideBalance) { - return {hiddenAmount} {props.currency}; + return ( + + {hiddenAmount} {props.currency} + + ); } if (props.amount == null) { From d8b8b4c143e5e28632f4904b063446160d00f911 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 5 Oct 2022 19:59:39 +0300 Subject: [PATCH 106/199] lint fixes --- .../ergo-connector/components/signin/AddCollateralPage.js | 2 +- .../ergo-connector/components/signin/CardanoSignTxPage.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js index c4039ae647..ad0a4c007b 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js @@ -1,6 +1,6 @@ /* eslint-disable no-nested-ternary */ // @flow -import React, { Component } from 'react'; +import { Component } from 'react'; import type { Node } from 'react'; import { intlShape, defineMessages, FormattedHTMLMessage } from 'react-intl'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js index 77ea6750a5..220c33760e 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js @@ -2,9 +2,9 @@ // @flow import React, { Component } from 'react'; import type { Node } from 'react'; -import { intlShape, defineMessages, FormattedHTMLMessage } from 'react-intl'; +import { intlShape, defineMessages } from 'react-intl'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; -import { Button, Typography, Alert } from '@mui/material'; +import { Button, Typography } from '@mui/material'; import TextField from '../../../components/common/TextField'; import globalMessages from '../../../i18n/global-messages'; import { observer } from 'mobx-react'; @@ -328,7 +328,7 @@ class SignTxPage extends Component { const walletPasswordField = form.$('walletPassword'); const { intl } = this.context; - const { txData, onCancel, connectedWebsite, submissionError, signData } = this.props; + const { txData, onCancel, connectedWebsite, signData } = this.props; const { isSubmitting } = this.state; From 3c60b5e5903d9ae32140c2a728ada9b99bc0db8c Mon Sep 17 00:00:00 2001 From: Patriciu Nista Date: Thu, 6 Oct 2022 12:00:54 +0200 Subject: [PATCH 107/199] Refactored links --- .../components/signin/AddCollateralPage.js | 44 ++++++++++++++++--- .../app/i18n/global-messages.js | 4 ++ .../app/i18n/locales/en-US.json | 3 +- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js index ad0a4c007b..a72529f6d5 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js @@ -2,9 +2,9 @@ // @flow import { Component } from 'react'; import type { Node } from 'react'; -import { intlShape, defineMessages, FormattedHTMLMessage } from 'react-intl'; +import { intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage } from 'react-intl'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; -import { Button, Typography, Alert } from '@mui/material'; +import { Button, Typography, Alert, Link } from '@mui/material'; import TextField from '../../../components/common/TextField'; import globalMessages from '../../../i18n/global-messages'; import { observer } from 'mobx-react'; @@ -50,7 +50,7 @@ const messages = defineMessages({ reorgMessage: { id: 'connector.signin.reorg.message', defaultMessage: - '!!!To interact with smart contract in Cardano you should add collateral, which means to make a 0 ADA transaction.

    It is a guarantee that prevent from failing smart contracts and scams. Learn more about collateral.
    ', + '!!!To interact with {smartContracts} in Cardano you should add collateral, which means to make a 0 ADA transaction.{lineBreak}{lineBreak}It is a guarantee that prevent from failing smart contracts and scams. {learnMore} about collateral.', }, sendError: { id: 'connector.signin.error.sendError', @@ -201,6 +201,28 @@ class AddCollateralPage extends Component { const txAmount = txData.amount.get(txAmountDefaultToken) ?? new BigNumber('0'); const txFeeAmount = new BigNumber(txData.fee.amount).negated(); + const learnMoreLink = ( + + {intl.formatMessage(globalMessages.learnMore)} + + ); + + const smartContractsLink = ( + + {intl.formatMessage(globalMessages.smartContracts).toLowerCase()} + + ); + return ( @@ -215,9 +237,17 @@ class AddCollateralPage extends Component { - - - +
    + , + }} + /> +
    + { gap="16px" > - {intl.formatMessage(globalMessages.labels.amount)} + {intl.formatMessage(globalMessages.amount)} {this.renderAmountDisplay({ entry: { diff --git a/packages/yoroi-extension/app/i18n/global-messages.js b/packages/yoroi-extension/app/i18n/global-messages.js index 323066e8d2..81b233476a 100644 --- a/packages/yoroi-extension/app/i18n/global-messages.js +++ b/packages/yoroi-extension/app/i18n/global-messages.js @@ -168,6 +168,10 @@ const globalMessages: * = defineMessages({ id: 'global.labels.LearnMore', defaultMessage: '!!!Learn more', }, + smartContracts: { + id: 'global.labels.smartContracts', + defaultMessage: '!!!Smart contracts', + }, walletLabel: { id: 'settings.menu.wallet.link.label', defaultMessage: '!!!Wallet', diff --git a/packages/yoroi-extension/app/i18n/locales/en-US.json b/packages/yoroi-extension/app/i18n/locales/en-US.json index 40c224f3fb..7f776224e4 100644 --- a/packages/yoroi-extension/app/i18n/locales/en-US.json +++ b/packages/yoroi-extension/app/i18n/locales/en-US.json @@ -78,7 +78,7 @@ "connector.signin.transactionFee": "Transaction Fee", "connector.signin.error.incorrectPasswordError": "Incorrect wallet password.", "connector.signin.error.sendError": "An error occured when sending the transaction.", - "connector.signin.reorg.message": "To interact with smart contract in Cardano you should add collateral, which means to make a 0 ADA transaction.

    It is a guarantee that prevent from failing smart contracts and scams. Learn more about collateral.
    ", + "connector.signin.reorg.message": "To interact with {smartContracts} in Cardano you should add collateral, which means to make a 0 ADA transaction.{lineBreak}{lineBreak}It is a guarantee that prevent from failing smart contracts and scams. {learnMore} about collateral.", "connector.signin.reorg.title": "Add Collateral", "crash.screen.title": "Yoroi crashed", "daedalusTransfer.error.noTransferTxError": "There is no transaction to be sent.", @@ -132,6 +132,7 @@ "global.label.assets": "assets", "global.label.choose": "Choose", "global.labels.LearnMore": "Learn more", + "global.labels.smartContracts": "Smart contracts", "global.labels.addMemo": "Add memo", "global.labels.amount": "Amount", "global.labels.back": "Back", From 213645d2b395e61eaf10d59e6c3407371c4a19ba Mon Sep 17 00:00:00 2001 From: Patriciu Nista Date: Thu, 6 Oct 2022 12:06:31 +0200 Subject: [PATCH 108/199] Removed unused import --- .../app/ergo-connector/components/signin/AddCollateralPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js index a72529f6d5..f48fba943f 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js @@ -2,7 +2,7 @@ // @flow import { Component } from 'react'; import type { Node } from 'react'; -import { intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage } from 'react-intl'; +import { intlShape, defineMessages, FormattedMessage } from 'react-intl'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; import { Button, Typography, Alert, Link } from '@mui/material'; import TextField from '../../../components/common/TextField'; From e39ed7de915d558ecec4e72d56af6f909ab6c552 Mon Sep 17 00:00:00 2001 From: Patriciu Nista Date: Thu, 6 Oct 2022 12:10:23 +0200 Subject: [PATCH 109/199] Refactor variable names --- .../components/signin/AddCollateralPage.js | 11 +++++------ packages/yoroi-extension/app/i18n/locales/en-US.json | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js index f48fba943f..6af81151e5 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js @@ -50,7 +50,7 @@ const messages = defineMessages({ reorgMessage: { id: 'connector.signin.reorg.message', defaultMessage: - '!!!To interact with {smartContracts} in Cardano you should add collateral, which means to make a 0 ADA transaction.{lineBreak}{lineBreak}It is a guarantee that prevent from failing smart contracts and scams. {learnMore} about collateral.', + '!!!To interact with {smartContractsLink} in Cardano you should add collateral, which means to make a 0 ADA transaction.{lineBreak}{lineBreak}It is a guarantee that prevent from failing smart contracts and scams. {learnMoreLink} about collateral.', }, sendError: { id: 'connector.signin.error.sendError', @@ -237,17 +237,16 @@ class AddCollateralPage extends Component { -
    + , }} /> -
    - +
    Date: Thu, 6 Oct 2022 13:23:07 +0200 Subject: [PATCH 110/199] added layout max width --- .../ergo-connector/components/signin/AddCollateralPage.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js index 6af81151e5..dc154cef05 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/AddCollateralPage.js @@ -225,7 +225,7 @@ class AddCollateralPage extends Component { return ( - + { )} - + Date: Thu, 6 Oct 2022 13:59:23 +0200 Subject: [PATCH 112/199] Fix misaligned fiat values --- .../ergo-connector/components/signin/CardanoSignTxPage.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js index 220c33760e..14c5bbc3de 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js @@ -359,13 +359,13 @@ class SignTxPage extends Component { {intl.formatMessage(signTxMessages.transactionFee)} - + {this.renderAmountDisplay({ entry: { identifier: txData.fee.tokenId, @@ -381,13 +381,13 @@ class SignTxPage extends Component { mt="10px" display="flex" justifyContent="space-between" - alignItems="center" + alignItems="flex-start" borderRadius="6px" backgroundColor="var(--yoroi-palette-primary-300)" color="var(--yoroi-palette-common-white)" > {intl.formatMessage(signTxMessages.totalAmount)} - + {this.renderAmountDisplay({ entry: { identifier: txAmountDefaultToken, From b4d6e06650f89b044f10a029656cba798949bcfe Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 7 Oct 2022 10:56:00 +0300 Subject: [PATCH 113/199] Collateral updates --- .../features/connector-get-collateral.feature | 43 +++++++++++++------ .../step_definitions/connector-steps.js | 20 ++++++--- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/packages/yoroi-extension/features/connector-get-collateral.feature b/packages/yoroi-extension/features/connector-get-collateral.feature index 30682e6c02..088bcd6232 100644 --- a/packages/yoroi-extension/features/connector-get-collateral.feature +++ b/packages/yoroi-extension/features/connector-get-collateral.feature @@ -34,7 +34,7 @@ Feature: dApp connector get collateral And The wallet shelley-simple-15 is connected to the website localhost Then The dApp should see collateral: {"utxo_id":"3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba211","tx_hash":"3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba21","tx_index":1,"receiver":"addr1qyv7qlaucathxkwkc503ujw0rv9lfj2rkj96feyst2rs9ey4tr5knj4fu4adelzqhxg8adu5xca4jra0gtllfrpcawyqzajfkn","amount":"5500000","assets":[]} for 490000 -@dApp-1025 + @dApp-1025 Scenario: dApp, authorized wallet, get collateral, connector popup Given There is a Shelley wallet stored named shelley-simple-15 Then Revamp. I switch to revamp version @@ -47,15 +47,32 @@ Feature: dApp connector get collateral And The access request should succeed And The wallet shelley-simple-15 is connected to the website localhost Then I ask to get Collateral for 2000000 Utxos - Then I should see the connector popup to Add Collateral - And I should see the collateral fee data: - | fee | - | 0.171177 | - And I should see the collateral from address info: - | fromAddress | fromAddressAmount | - | addr1...ajfkn | -5.5 | - And I should see the collateral to addresses info: - | toAddresses | toAddressesAmount | - | addr1...qef6t | +1 | - | addr1...psz23 | +1 | - | addr1...psz23 | +3.328823 | \ No newline at end of file + Then I should see the connector popup to Add Collateral with fee info + | fee | + | 0.171177 | + Then I enter the spending password asdfasdfasdf and click confirm + Then The popup window should be closed + Then The dApp should receive collateral + | forAmount | amount | receiver | + | 2000000 | 1000000 | addr1qy245684mdhpwzs0p37jz8pymn5g9v37rqjy78c59f06xau4tr5knj4fu4adelzqhxg8adu5xca4jra0gtllfrpcawyqdqef6t | + + @dApp-1026 + Scenario: dApp, anonymous wallet, get collateral, connector popup + Given There is a Shelley wallet stored named shelley-simple-15 + Then Revamp. I switch to revamp version + Then I open the mock dApp tab + And I request anonymous access to Yoroi + Then I should see the connector popup for connection + And I select the only wallet named shelley-simple-15 with 5.5 balance + Then The popup window should be closed + And The access request should succeed + And The wallet shelley-simple-15 is connected to the website localhost + Then I ask to get Collateral for 2000000 Utxos + Then I should see the connector popup to Add Collateral with fee info + | fee | + | 0.171177 | + Then I enter the spending password asdfasdfasdf and click confirm + Then The popup window should be closed + Then The dApp should receive collateral + | forAmount | amount | receiver | + | 2000000 | 1000000 | addr1qy245684mdhpwzs0p37jz8pymn5g9v37rqjy78c59f06xau4tr5knj4fu4adelzqhxg8adu5xca4jra0gtllfrpcawyqdqef6t | \ No newline at end of file diff --git a/packages/yoroi-extension/features/step_definitions/connector-steps.js b/packages/yoroi-extension/features/step_definitions/connector-steps.js index 1c5eb9b7a3..416f43513c 100644 --- a/packages/yoroi-extension/features/step_definitions/connector-steps.js +++ b/packages/yoroi-extension/features/step_definitions/connector-steps.js @@ -381,9 +381,7 @@ When(/^I ask to get Collateral for (.+) Utxos$/, async function (utxos) { await this.mockDAppPage.addCollateral(utxos); }); -Then( - /^The dApp should see collateral: (.+) for (.+)$/, - async function (expectedCollateral, utxosAmount) { +Then(/^The dApp should see collateral: (.+) for (.+)$/, async function (expectedCollateral, utxosAmount) { this.webDriverLogger.info( `Step: The dApp should see collateral: ${expectedCollateral} for ${utxosAmount}` ); @@ -394,10 +392,22 @@ Then( } ); -Then(/^I should see the connector popup to Add Collateral$/, async function () { - this.webDriverLogger.info(`Step: I should see the connector popup to Add Collateral`); +Then(/^The dApp should receive collateral$/, async function (table) { + const fields = table.hashes()[0]; + const utxosAmount = fields.forAmount; + const collateral = await this.mockDAppPage.getCollateralUtxos(utxosAmount); + const collateralJson = JSON.parse(collateral)[0]; + expect(collateralJson.amount, 'Amount is different').to.equal(fields.amount); + expect(collateralJson.receiver, 'Receiver is different').to.equal(fields.receiver); +}); + +Then(/^I should see the connector popup to Add Collateral with fee info$/, async function (table) { + this.webDriverLogger.info(`Step: I should see the connector popup to Add Collateral with fee info`); await connectorPopUpIsDisplayed(this); await this.waitForElement(addCollateralTitle); + const fields = table.hashes()[0]; + const realFee = await getTransactionFee(this); + expect(realFee, 'Fee is different').to.equal(fields.fee); }); Then(/^I should see the collateral fee data:$/, async function (table) { From 4c1c7cf1e530fba31dc57701baddf2b1f4f6e5da Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 7 Oct 2022 10:59:55 +0300 Subject: [PATCH 114/199] Tests names update --- .../connector-anonymous-wallet-errors-checking.feature | 4 ++-- .../connector-authorized-wallet-errors-checking.feature | 4 ++-- .../features/connector-get-collateral.feature | 8 ++++---- .../yoroi-extension/features/connector-sign-data.feature | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/yoroi-extension/features/connector-anonymous-wallet-errors-checking.feature b/packages/yoroi-extension/features/connector-anonymous-wallet-errors-checking.feature index 4851d98340..e0da07c331 100644 --- a/packages/yoroi-extension/features/connector-anonymous-wallet-errors-checking.feature +++ b/packages/yoroi-extension/features/connector-anonymous-wallet-errors-checking.feature @@ -55,7 +55,7 @@ Feature: dApp connector anonymous wallet errors checking And The user reject for signing is received @dApp-1015 - Scenario: dApp, anonymous wallet, unused address, signing data, cancel signing + Scenario: dApp, anonymous wallet, unused address, signing data, cancel signing (DAPP-1015) And I request anonymous access to Yoroi Then I should see the connector popup for connection And I select the only wallet named shelley-simple-15 with 5.5 balance @@ -71,7 +71,7 @@ Feature: dApp connector anonymous wallet errors checking And The user reject for signing data is received @dApp-1016 - Scenario: dApp, anonymous wallet, used address, signing data, cancel signing + Scenario: dApp, anonymous wallet, used address, signing data, cancel signing (DAPP-1016) And I request anonymous access to Yoroi Then I should see the connector popup for connection And I select the only wallet named shelley-simple-15 with 5.5 balance diff --git a/packages/yoroi-extension/features/connector-authorized-wallet-errors-checking.feature b/packages/yoroi-extension/features/connector-authorized-wallet-errors-checking.feature index 79fcbf5e1b..6e030ab477 100644 --- a/packages/yoroi-extension/features/connector-authorized-wallet-errors-checking.feature +++ b/packages/yoroi-extension/features/connector-authorized-wallet-errors-checking.feature @@ -74,7 +74,7 @@ Feature: dApp connector errors checking And The user reject for signing is received @dApp-1017 - Scenario: dApp, authorized wallet, unused address, signing data, cancel signing + Scenario: dApp, authorized wallet, unused address, signing data, cancel signing (DAPP-1017) And I request access to Yoroi Then I should see the connector popup for connection And I select the only wallet named shelley-simple-15 with 5.5 balance @@ -91,7 +91,7 @@ Feature: dApp connector errors checking And The user reject for signing data is received @dApp-1018 - Scenario: dApp, authorized wallet, used address, signing data, cancel signing + Scenario: dApp, authorized wallet, used address, signing data, cancel signing (DAPP-1018) And I request access to Yoroi Then I should see the connector popup for connection And I select the only wallet named shelley-simple-15 with 5.5 balance diff --git a/packages/yoroi-extension/features/connector-get-collateral.feature b/packages/yoroi-extension/features/connector-get-collateral.feature index 088bcd6232..246e3535fb 100644 --- a/packages/yoroi-extension/features/connector-get-collateral.feature +++ b/packages/yoroi-extension/features/connector-get-collateral.feature @@ -8,7 +8,7 @@ Feature: dApp connector get collateral @dApp-1023 - Scenario: dApp, anonymous wallet, get collateral + Scenario: dApp, anonymous wallet, get collateral (DAPP-1023) Given There is a Shelley wallet stored named shelley-simple-15 Then Revamp. I switch to revamp version Then I open the mock dApp tab @@ -21,7 +21,7 @@ Feature: dApp connector get collateral Then The dApp should see collateral: {"utxo_id":"3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba211","tx_hash":"3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba21","tx_index":1,"receiver":"addr1qyv7qlaucathxkwkc503ujw0rv9lfj2rkj96feyst2rs9ey4tr5knj4fu4adelzqhxg8adu5xca4jra0gtllfrpcawyqzajfkn","amount":"5500000","assets":[]} for 490000 @dApp-1024 - Scenario: dApp, authorized wallet, get collateral + Scenario: dApp, authorized wallet, get collateral (DAPP-1024) Given There is a Shelley wallet stored named shelley-simple-15 Then Revamp. I switch to revamp version Then I open the mock dApp tab @@ -35,7 +35,7 @@ Feature: dApp connector get collateral Then The dApp should see collateral: {"utxo_id":"3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba211","tx_hash":"3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba21","tx_index":1,"receiver":"addr1qyv7qlaucathxkwkc503ujw0rv9lfj2rkj96feyst2rs9ey4tr5knj4fu4adelzqhxg8adu5xca4jra0gtllfrpcawyqzajfkn","amount":"5500000","assets":[]} for 490000 @dApp-1025 - Scenario: dApp, authorized wallet, get collateral, connector popup + Scenario: dApp, authorized wallet, get collateral, connector popup (DAPP-1025) Given There is a Shelley wallet stored named shelley-simple-15 Then Revamp. I switch to revamp version Then I open the mock dApp tab @@ -57,7 +57,7 @@ Feature: dApp connector get collateral | 2000000 | 1000000 | addr1qy245684mdhpwzs0p37jz8pymn5g9v37rqjy78c59f06xau4tr5knj4fu4adelzqhxg8adu5xca4jra0gtllfrpcawyqdqef6t | @dApp-1026 - Scenario: dApp, anonymous wallet, get collateral, connector popup + Scenario: dApp, anonymous wallet, get collateral, connector popup (DAPP-1026) Given There is a Shelley wallet stored named shelley-simple-15 Then Revamp. I switch to revamp version Then I open the mock dApp tab diff --git a/packages/yoroi-extension/features/connector-sign-data.feature b/packages/yoroi-extension/features/connector-sign-data.feature index 500ae361c5..f901c723c2 100644 --- a/packages/yoroi-extension/features/connector-sign-data.feature +++ b/packages/yoroi-extension/features/connector-sign-data.feature @@ -10,7 +10,7 @@ Feature: dApp connector data signing Then I open the mock dApp tab @dApp-1019 - Scenario: dApp, anonymous wallet, unused address, sign Cardano data + Scenario: dApp, anonymous wallet, unused address, sign Cardano data (DAPP-1019) And I request anonymous access to Yoroi Then I should see the connector popup for connection And I select the only wallet named shelley-simple-15 with 5.5 balance @@ -29,7 +29,7 @@ Feature: dApp connector data signing Then The popup window should be closed @dApp-1020 - Scenario: dApp, anonymous wallet, used address, sign Cardano data + Scenario: dApp, anonymous wallet, used address, sign Cardano data (DAPP-1020) And I request anonymous access to Yoroi Then I should see the connector popup for connection And I select the only wallet named shelley-simple-15 with 5.5 balance @@ -48,7 +48,7 @@ Feature: dApp connector data signing Then The popup window should be closed @dApp-1021 - Scenario: dApp, authorised wallet, unused address, sign Cardano data + Scenario: dApp, authorised wallet, unused address, sign Cardano data (DAPP-1021) And I request access to Yoroi Then I should see the connector popup for connection And I select the only wallet named shelley-simple-15 with 5.5 balance @@ -68,7 +68,7 @@ Feature: dApp connector data signing Then The popup window should be closed @dApp-1022 - Scenario: dApp, authorised wallet, unused address, sign Cardano data + Scenario: dApp, authorised wallet, used address, sign Cardano data (DAPP-1022) And I request access to Yoroi Then I should see the connector popup for connection And I select the only wallet named shelley-simple-15 with 5.5 balance From 4005c9500e0e028f61660cbf9e4c94630cca8415 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 11 Oct 2022 13:13:12 +0200 Subject: [PATCH 115/199] Add IIFE to the window.onload function --- packages/yoroi-ergo-connector/example-cardano/index.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/yoroi-ergo-connector/example-cardano/index.js b/packages/yoroi-ergo-connector/example-cardano/index.js index 8c36426acf..94157467d3 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.js +++ b/packages/yoroi-ergo-connector/example-cardano/index.js @@ -908,11 +908,7 @@ function toggleConnectionUI(status) { } } -const onload = window.onload; -window.onload = function () { - if (onload) { - onload(); - } +window.onload = (() => { if (typeof window.cardano === 'undefined') { alertError('Cardano API not found'); } else { @@ -933,4 +929,4 @@ window.onload = function () { } ); } -}; +})(); From 1d76242083f75d40e96d2d42ce93bc175a012b9e Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 11 Oct 2022 13:31:42 +0200 Subject: [PATCH 116/199] Use MUI linear progress --- .../app/ergo-connector/components/ProgressBar.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js b/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js index c812ff07a6..f66f2334a5 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js +++ b/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js @@ -1,7 +1,8 @@ // @flow +import type { Node } from 'react'; import styles from './ProgressBar.scss'; +import LinearProgress from '@mui/material/LinearProgress'; -import type { Node } from 'react'; type Props = {| +step?: number, @@ -11,7 +12,7 @@ type Props = {| const ProgressBar = ({ step, max }: Props): Node => { return (
    - +
    ); }; From 3e95cf7177bbeb6ff7a9d85ba2d46a46940a8bba Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Wed, 12 Oct 2022 14:00:30 +0200 Subject: [PATCH 117/199] Fix flow --- .../app/ergo-connector/components/ProgressBar.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js b/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js index f66f2334a5..90edf9e996 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js +++ b/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js @@ -9,10 +9,13 @@ type Props = {| +max?: number, |}; -const ProgressBar = ({ step, max }: Props): Node => { +const ProgressBar = (props: Props): Node => { + const step = props.step || 1; + const max = props.max || 3 + return (
    - +
    ); }; From c5de6508fb29ab6d0357449652921b97fa0a3cd3 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Wed, 12 Oct 2022 14:02:58 +0200 Subject: [PATCH 118/199] Remove condition over values --- .../app/ergo-connector/components/ProgressBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js b/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js index 90edf9e996..2c37b018fd 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js +++ b/packages/yoroi-extension/app/ergo-connector/components/ProgressBar.js @@ -15,7 +15,7 @@ const ProgressBar = (props: Props): Node => { return (
    - +
    ); }; From 495326b2a7531805427b83e66786094c00a52992 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Mon, 17 Oct 2022 13:13:51 +0200 Subject: [PATCH 119/199] Show connected sites base on the revamp --- packages/yoroi-extension/app/i18n/global-messages.js | 7 ++++++- packages/yoroi-extension/app/i18n/locales/en-US.json | 1 + .../app/stores/stateless/sidebarCategories.js | 12 ++++++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/app/i18n/global-messages.js b/packages/yoroi-extension/app/i18n/global-messages.js index 81b233476a..7c6798c08a 100644 --- a/packages/yoroi-extension/app/i18n/global-messages.js +++ b/packages/yoroi-extension/app/i18n/global-messages.js @@ -1013,8 +1013,13 @@ export const connectorMessages: * = defineMessages({ }, dappConnector: { id: 'connector.appName', - defaultMessage: 'Dapp Connector', + defaultMessage: '!!!Dapp Connector', }, + connector: { + id: 'connector.appNameShort', + defaultMessage: '!!!Connector' + } + }); export function listOfTranslators(contributorsList: string, contributorsAck: string): string { diff --git a/packages/yoroi-extension/app/i18n/locales/en-US.json b/packages/yoroi-extension/app/i18n/locales/en-US.json index a4bf0bc24c..14c433cdad 100644 --- a/packages/yoroi-extension/app/i18n/locales/en-US.json +++ b/packages/yoroi-extension/app/i18n/locales/en-US.json @@ -55,6 +55,7 @@ "buysell.dialog.manual": "I will add my address manually", "buysell.dialog.selectAddress": "Please select the receiving address. This will be shared with the third party provider called Changelly for the buy / sell of ADA. ", "connector.appName": "Dapp Connector", + "connector.appNameShort": "Connector", "connector.connect.connectedWallets": "Connected Wallets", "connector.connect.connectedWallets.active": "Active", "connector.connect.noWebsitesConnected": "You don't have any websites connected yet", diff --git a/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js b/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js index 9d4c989c97..e995c9ac7f 100644 --- a/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js +++ b/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js @@ -94,8 +94,7 @@ export const TRANSFER_PAGE: SidebarCategory = registerCategory({ }); -export const DAPP_CONNECTOR: SidebarCategory = registerCategory({ - className: 'dapp-connector', +export const CONNECTED_WEBSITES: SidebarCategory = registerCategory({ route: ROUTES.DAPP_CONNECTOR.CONNECTED_WEBSITES, icon: dappConnectorIcon, label: connectorMessages.dappConnector, @@ -108,6 +107,7 @@ export const NOTICE_BOARD: SidebarCategory = registerCategory({ icon: noticeBoardIcon, isVisible: _request => !environment.isProduction(), }); + export type SidebarCategoryRevamp = {| +className: string, +route: string, @@ -119,6 +119,7 @@ export type SidebarCategoryRevamp = {| currentRoute: string, |}) => boolean, |}; + // TODO: Fix routes and isVisible prop export const allCategoriesRevamp: Array = [ { @@ -159,6 +160,13 @@ export const allCategoriesRevamp: Array = [ // $FlowFixMe[prop-missing] isVisible: request => asGetStakingKey(request.selected) != null, }, + { + className: 'connected-websites', + route: ROUTES.DAPP_CONNECTOR.CONNECTED_WEBSITES, + icon: dappConnectorIcon, + label: connectorMessages.connector, + isVisible: _request => true, + }, // { // className: 'swap', // route: '/swap', From 44c07cb6c8e4113c2cb192e278e88632277e490e Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 17 Oct 2022 16:03:12 +0300 Subject: [PATCH 120/199] Updated dApp tests --- packages/yoroi-extension/features/pages/sidebarPage.js | 1 + .../features/step_definitions/connector-steps.js | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/yoroi-extension/features/pages/sidebarPage.js b/packages/yoroi-extension/features/pages/sidebarPage.js index f8b8260680..f10645d855 100644 --- a/packages/yoroi-extension/features/pages/sidebarPage.js +++ b/packages/yoroi-extension/features/pages/sidebarPage.js @@ -11,6 +11,7 @@ export const stakingButton: LocatorObject = { locator: 'sidebar.staking', method export const assetsButton: LocatorObject = { locator: 'sidebar.assets', method: 'id' }; export const votingButton: LocatorObject = { locator: 'sidebar.voting', method: 'id' }; export const settingsButton: LocatorObject = { locator: 'sidebar.settings', method: 'id' }; +export const connectorButton: LocatorObject = { locator: 'connector.appNameShort', method: 'id' }; export const faqButton: LocatorObject = { locator: '.SidebarRevamp_faq', method: 'css' }; // Classic version elements diff --git a/packages/yoroi-extension/features/step_definitions/connector-steps.js b/packages/yoroi-extension/features/step_definitions/connector-steps.js index 416f43513c..55d0445cee 100644 --- a/packages/yoroi-extension/features/step_definitions/connector-steps.js +++ b/packages/yoroi-extension/features/step_definitions/connector-steps.js @@ -29,6 +29,7 @@ import { import { getSigningData, signMessageTitle } from '../pages/connector-signingDataPage'; import { addCollateralTitle } from '../pages/connector-getCollateralPage'; import { mockDAppName, extensionTabName, popupConnectorName } from '../support/windowManager'; +import { connectorButton } from '../pages/sidebarPage'; const userRejectMsg = 'user reject'; const userRejectSigningMsg = 'User rejected'; @@ -280,9 +281,8 @@ Then(/^The wallet (.+) is connected to the website (.+)$/, async function (walle `Step: The wallet ${walletName} is connected to the website ${websiteUrl}` ); await this.windowManager.switchTo(extensionTabName); - const connectedWebsitesAddress = `${this.getExtensionUrl()}#/connector/connected-websites`; // it should be reworked by using ui components when it is done - await this.driver.get(connectedWebsitesAddress); + await this.click(connectorButton); const wallets = await getWalletsWithConnectedWebsites(this); const result = wallets.filter( wallet => wallet.walletTitle === walletName && wallet.websiteTitle === websiteUrl @@ -294,9 +294,8 @@ Then(/^The wallet (.+) is connected to the website (.+)$/, async function (walle Then(/^I disconnect the wallet (.+) from the dApp (.+)$/, async function (walletName, dAppUrl) { this.webDriverLogger.info(`Step: I disconnect the wallet ${walletName} from the dApp ${dAppUrl}`); await this.windowManager.switchTo(extensionTabName); - const connectedWebsitesAddress = `${this.getExtensionUrl()}#/connector/connected-websites`; // it should be reworked by using ui components when it is done - await this.driver.get(connectedWebsitesAddress); + await this.click(connectorButton); await disconnectWallet(this, walletName, dAppUrl); }); From fec8a0de921c86f5e04b614da3fdf76950b13788 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 18 Oct 2022 12:33:37 +0200 Subject: [PATCH 121/199] Add missing class name --- .../yoroi-extension/app/stores/stateless/sidebarCategories.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js b/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js index e995c9ac7f..e4052fd464 100644 --- a/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js +++ b/packages/yoroi-extension/app/stores/stateless/sidebarCategories.js @@ -95,6 +95,7 @@ export const TRANSFER_PAGE: SidebarCategory = registerCategory({ export const CONNECTED_WEBSITES: SidebarCategory = registerCategory({ + className: 'dapp-connector', route: ROUTES.DAPP_CONNECTOR.CONNECTED_WEBSITES, icon: dappConnectorIcon, label: connectorMessages.dappConnector, From f2881b5cc9a2d6044b6a5287b39beb3081553b56 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 19 Oct 2022 13:08:08 +0300 Subject: [PATCH 122/199] set modern theme default for tests --- packages/yoroi-extension/app/stores/base/BaseProfileStore.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/yoroi-extension/app/stores/base/BaseProfileStore.js b/packages/yoroi-extension/app/stores/base/BaseProfileStore.js index 7c31d6f988..9eab767a54 100644 --- a/packages/yoroi-extension/app/stores/base/BaseProfileStore.js +++ b/packages/yoroi-extension/app/stores/base/BaseProfileStore.js @@ -273,11 +273,6 @@ export default class BaseProfileStore } } - // THEMES.YOROI_MODERN is the default theme - // TODO: Tests were written for the old theme so we need to use it for testing - if (environment.isTest()) { - return THEMES.YOROI_CLASSIC; - } return THEMES.YOROI_MODERN; } From d73a734fccde29e81ec0b13c1d043ff0ee3c60ef Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 19 Oct 2022 15:54:09 +0300 Subject: [PATCH 123/199] using correct locators --- .../features/step_definitions/wallet-creation-steps.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js index fb4c50d988..a62d031237 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js @@ -7,6 +7,7 @@ import { expect, assert } from 'chai'; import { checkErrorByTranslationId } from './common-steps'; import { clearButton, + createNormalWalletButton, createOptionDialog, createPaperWalletButton, createPersonalWalletButton, @@ -37,8 +38,8 @@ When(/^I select the currency ([^"]*)$/, async function (currency) { }); When(/^I select Create Wallet$/, async function () { - await this.waitForElement(pickUpCurrencyDialog); - await this.click(createWalletButton); + await this.waitForElement(createOptionDialog); + await this.click(createNormalWalletButton); }); When(/^I select Paper Wallet$/, async function () { await this.waitForElement(createOptionDialog); From 4c00f62b03fd12083b06dc6d53bbdaa0029b3513 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 19 Oct 2022 15:56:05 +0300 Subject: [PATCH 124/199] using the modern theme --- packages/yoroi-extension/features/pages/mainWindowPage.js | 1 + .../features/step_definitions/common-steps.js | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/yoroi-extension/features/pages/mainWindowPage.js b/packages/yoroi-extension/features/pages/mainWindowPage.js index 7b58e5161a..c644359b3a 100644 --- a/packages/yoroi-extension/features/pages/mainWindowPage.js +++ b/packages/yoroi-extension/features/pages/mainWindowPage.js @@ -3,6 +3,7 @@ import type { LocatorObject } from '../support/webdriver'; export const yoroiClassic: LocatorObject = { locator: '.YoroiClassic', method: 'css' }; +export const yoroiModern: LocatorObject = { locator: '.YoroiModern', method: 'css' }; export const serverErrorBanner: LocatorObject = { locator: '.ServerErrorBanner_serverError', method: 'css', diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 7b81070d04..ccb02897bf 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -105,7 +105,7 @@ import { uriAcceptComponent, uriPromptForm, } from '../pages/uriPromptPage'; -import { yoroiClassic } from '../pages/mainWindowPage'; +import { yoroiModern } from '../pages/mainWindowPage'; import { extensionTabName, WindowManager } from '../support/windowManager'; import { MockDAppWebpage } from '../mock-dApp-webpage'; @@ -528,7 +528,7 @@ Given(/^I refresh the page$/, async function () { await this.driver.navigate().refresh(); // wait for page to refresh await this.driver.sleep(halfSecond); - await this.waitForElement(yoroiClassic); + await this.waitForElement(yoroiModern); }); Given(/^I restart the browser$/, async function () { @@ -537,7 +537,7 @@ Given(/^I restart the browser$/, async function () { await this.driver.navigate().refresh(); // wait for page to refresh await this.driver.sleep(halfSecond); - await this.waitForElement(yoroiClassic); + await this.waitForElement(yoroiModern); }); Given(/^There is no wallet stored$/, async function () { @@ -564,7 +564,7 @@ Given(/^I import a snapshot named ([^"]*)$/, async function (snapshotName) { await this.driver.navigate().refresh(); // wait for page to refresh await this.driver.sleep(oneSecond + halfSecond); - await this.waitForElement(yoroiClassic); + await this.waitForElement(yoroiModern); }); async function setLedgerWallet(client, serial) { From dc38a2a6eec1d646f543112f24f0a5614d14be99 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 19 Oct 2022 17:01:41 +0300 Subject: [PATCH 125/199] using the right way to find an element --- .../features/pages/newWalletPages.js | 7 +++++++ .../step_definitions/wallet-creation-steps.js | 18 +++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/yoroi-extension/features/pages/newWalletPages.js b/packages/yoroi-extension/features/pages/newWalletPages.js index 31470013b3..fcf42fa032 100644 --- a/packages/yoroi-extension/features/pages/newWalletPages.js +++ b/packages/yoroi-extension/features/pages/newWalletPages.js @@ -2,6 +2,7 @@ import type { LocatorObject } from '../support/webdriver'; +export const seedPhrasePlaceholder = 'Tap each word in the correct order to verify your recovery phrase'; export const connectHwButton: LocatorObject = { locator: '.WalletAdd_btnConnectHW', method: 'css' }; export const createWalletButton: LocatorObject = { locator: '.WalletAdd_btnCreateWallet', @@ -132,6 +133,12 @@ export const clearButton: LocatorObject = { locator: "//button[contains(text(), 'Clear')]", method: 'xpath', }; +export const getRecoveryPhraseWord = (indexNumber: number): LocatorObject => { + return { + locator: `//div[@class='WalletRecoveryPhraseEntryDialog_words']//button[${indexNumber}]`, + method: 'xpath' + }; +}; // Paper Wallet dialog export const paperWalletDialogSelect: LocatorObject = { diff --git a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js index a62d031237..61e2395723 100644 --- a/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/wallet-creation-steps.js @@ -3,7 +3,7 @@ import { When, Then } from 'cucumber'; import { By } from 'selenium-webdriver'; import i18n from '../support/helpers/i18n-helpers'; -import { expect, assert } from 'chai'; +import { expect } from 'chai'; import { checkErrorByTranslationId } from './common-steps'; import { clearButton, @@ -18,10 +18,12 @@ import { createWalletPasswordInput, createWalletRepeatPasswordInput, getCurrencyButton, + getRecoveryPhraseWord, pickUpCurrencyDialog, recoveryPhraseButton, recoveryPhraseConfirmButton, securityWarning, + seedPhrasePlaceholder, walletRecoveryPhraseMnemonicComponent, } from '../pages/newWalletPages'; import { continueButton } from '../pages/basicSetupPage'; @@ -115,16 +117,10 @@ When(/^I copy and enter the displayed mnemonic phrase$/, async function () { When(/^I enter random mnemonic phrase$/, async function () { await this.click(recoveryPhraseButton); for (let i = 15; i > 1; i--) { - await this.click({ - locator: `//div[@class='WalletRecoveryPhraseEntryDialog_words']//button[${i}]`, - method: 'xpath', - }); + await this.click(getRecoveryPhraseWord(i)); } - const words = await this.driver.findElement(By.css('.WalletRecoveryPhraseMnemonic_component')); - words - .getText() - .then(text => expect(text).to.not.equal('')) - .catch(err => assert.fail(err.message)); + const allWordsComponentText = await this.getText(walletRecoveryPhraseMnemonicComponent); + expect(allWordsComponentText).to.not.equal(seedPhrasePlaceholder); }); Then(/^I click Clear button$/, async function () { @@ -132,7 +128,7 @@ Then(/^I click Clear button$/, async function () { }); Then(/^I see All selected words are cleared$/, async function () { - await this.waitUntilText(walletRecoveryPhraseMnemonicComponent, '', 5000); + await this.waitUntilText(walletRecoveryPhraseMnemonicComponent, seedPhrasePlaceholder, 5000); }); Then(/^I should stay in the create wallet dialog$/, async function () { From f22391c8702a5b36643a84a8897a26059558ae27 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 19 Oct 2022 17:07:36 +0300 Subject: [PATCH 126/199] rearranged scenarios --- .../features/wallet-creation.feature | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/packages/yoroi-extension/features/wallet-creation.feature b/packages/yoroi-extension/features/wallet-creation.feature index 8995d26479..dff1d6b4e8 100644 --- a/packages/yoroi-extension/features/wallet-creation.feature +++ b/packages/yoroi-extension/features/wallet-creation.feature @@ -19,6 +19,24 @@ Feature: Wallet creation And I copy and enter the displayed mnemonic phrase Then I should see the opened wallet with name "Created Wallet" + @it-7 + Scenario Outline: Wallet can't be created if its password doesn't meet complexity requirements (IT-7) + When I click the create button + Then I select the currency cardano + Then I select Create Wallet + And I enter the name "Created Wallet" + And I enter the created wallet password: + | password | repeatedPassword | + | | | + Then I see the submit button is disabled + And I should see the invalid password error message: + | message | + | global.errors.invalidWalletPassword | + + Examples: + | wrongPassword | | + | Secre1 | too short | + @it-9 Scenario: Wallet access after browser restart (IT-9) When I click the create button @@ -35,6 +53,28 @@ Feature: Wallet creation When I restart the browser Then I should see the opened wallet with name "Created Wallet" + @it-16 + Scenario Outline: Wallet can't be created if wallet name doesn't meet requirements (IT-16) + When I click the create button + Then I select the currency cardano + Then I select Create Wallet + And I enter the name "Created Wallet" + And I enter the created wallet password: + | password | repeatedPassword | + | asdfasdfasdf | asdfasdfasdf | + And I clear the name "Created Wallet" + Then I see the submit button is disabled + And I should stay in the create wallet dialog + And I enter the name "" + Then I see the submit button is disabled + And I should stay in the create wallet dialog + And I should see "Wallet name requires at least 1 and at most 40 letters." error message: + | message | + | global.errors.invalidWalletName | + Examples: + | invalidWalletName | | + | qwertyuiopasdfghjklzxcvbnmzxcvbnmlkjhgfds|41 letters name| + @it-18 Scenario: Mnemonic words can be cleared by clicking "Clear button" on wallet creation screen (IT-18) When I click the create button @@ -79,48 +119,8 @@ Feature: Wallet creation | message | | wallet.backup.privacy.warning.dialog.checkbox.label.nobodyWatching | - @it-16 - Scenario Outline: Wallet can't be created if wallet name doesn't meet requirements (IT-16) - When I click the create button - Then I select the currency cardano - Then I select Create Wallet - And I enter the name "Created Wallet" - And I enter the created wallet password: - | password | repeatedPassword | - | asdfasdfasdf | asdfasdfasdf | - And I clear the name "Created Wallet" - Then I see the submit button is disabled - And I should stay in the create wallet dialog - And I enter the name "" - Then I see the submit button is disabled - And I should stay in the create wallet dialog - And I should see "Wallet name requires at least 1 and at most 40 letters." error message: - | message | - | global.errors.invalidWalletName | - Examples: - | invalidWalletName | | - | qwertyuiopasdfghjklzxcvbnmzxcvbnmlkjhgfds|41 letters name| - - @it-7 - Scenario Outline: Wallet can't be created if its password doesn't meet complexity requirements (IT-7) - When I click the create button - Then I select the currency cardano - Then I select Create Wallet - And I enter the name "Created Wallet" - And I enter the created wallet password: - | password | repeatedPassword | - | | | - Then I see the submit button is disabled - And I should see the invalid password error message: - | message | - | global.errors.invalidWalletPassword | - - Examples: - | wrongPassword | | - | Secre1 | too short | - @it-131 - Scenario: Wallet creation (IT-131) + Scenario: Wallet creation. Ergo (IT-131) When I click the create button Then I select the currency ergo Then I select Create Wallet From 41c3ac54835f8930682b61f3bc832a1dab437c95 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 19 Oct 2022 18:37:37 +0300 Subject: [PATCH 127/199] using the correct locator --- packages/yoroi-extension/features/pages/newWalletPages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/pages/newWalletPages.js b/packages/yoroi-extension/features/pages/newWalletPages.js index fcf42fa032..e18b42ed76 100644 --- a/packages/yoroi-extension/features/pages/newWalletPages.js +++ b/packages/yoroi-extension/features/pages/newWalletPages.js @@ -38,7 +38,7 @@ export const restore24WordWallet: LocatorObject = { method: 'css', }; export const walletRestoreDialog: LocatorObject = { - locator: '.WalletRestoreOptionDialog', + locator: '.WalletRestoreDialog', method: 'css', }; export const getCurrencyButton = (currency: string): LocatorObject => { From 1b49b5c838729f1606280fdd21be3cfb5ff48de3 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 19 Oct 2022 19:24:34 +0300 Subject: [PATCH 128/199] removed the outdated test-case --- .../features/wallet-restoration.feature | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/packages/yoroi-extension/features/wallet-restoration.feature b/packages/yoroi-extension/features/wallet-restoration.feature index b4144f58f9..d88cfe87cc 100644 --- a/packages/yoroi-extension/features/wallet-restoration.feature +++ b/packages/yoroi-extension/features/wallet-restoration.feature @@ -189,71 +189,6 @@ Feature: Restore Wallet | recoveryPhrase | | | remind style lunch result accuse upgrade atom eight limit glance frequent eternal fashion borrow | 14-words phrase | - @it-92 @ignore - Scenario: Create & delete (3 wallets) (IT-92) - # wallet 1 - When I click the restore button for cardano - Then I select Byron-era 15-word wallet - And I enter the name "many-tx-wallet" - And I enter the recovery phrase: - | recoveryPhrase | - | final autumn bacon fold horse scissors act pole country focus task blush basket move view | - And I enter the restored wallet password: - | password | repeatedPassword | - | asdfasdfasdf | asdfasdfasdf | - And I click the "Restore Wallet" button - Then I should see a plate ZKTZ-4614 - Then I click the next button - Then I should see the opened wallet with name "many-tx-wallet" - # add wallet prop - Then I unselect the wallet - And I click to add an additional wallet - # wallet 2 (same as wallet 1) - When I click the restore button for cardano - Then I select Byron-era 15-word wallet - And I enter the name "Restored Wallet (copy)" - And I enter the recovery phrase: - | recoveryPhrase | - | final autumn bacon fold horse scissors act pole country focus task blush basket move view | - And I enter the restored wallet password: - | password | repeatedPassword | - | asdfasdfasdf | asdfasdfasdf | - And I click the "Restore Wallet" button - Then I should see a plate ZKTZ-4614 - Then I should see the wallet already exist window - Then I click the Open wallet button - Then I should see the opened wallet with name "many-tx-wallet" - # add wallet prep - Then I unselect the wallet - And I click to add an additional wallet - # copy the DB - Given I capture DB state snapshot - # wallet 3 (different wallet) - When I click the restore button for cardano - Then I select Byron-era 15-word wallet - And I enter the name "Restored Wallet 2" - And I enter the recovery phrase: - | recoveryPhrase | - | eight country switch draw meat scout mystery blade tip drift useless good keep usage title | - And I enter the restored wallet password: - | password | repeatedPassword | - | asdfasdfasdf | asdfasdfasdf | - And I click the "Restore Wallet" button - Then I should see a plate EAJD-7036 - Then I click the next button - Then I should see the opened wallet with name "Restored Wallet 2" - # remove wallet #2 - Then I navigate to the general settings screen - And I click on secondary menu "wallet" item - When I click on remove wallet - Then I click on the checkbox - And I click the next button - # wait for page to reload - Given I sleep for 5000 - # check removing didn't affect other wallets - Then I compare to DB state snapshot - And I am on the my wallets screen - @it-95 Scenario: Create & delete (1 wallet) (IT-95) # copy the DB From a017d63a8416647f96a6cbac9be207f2316bf3ff Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 19 Oct 2022 19:33:21 +0300 Subject: [PATCH 129/199] using the correct locator --- .../yoroi-extension/features/step_definitions/common-steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index ccb02897bf..8a3d00a269 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -350,7 +350,7 @@ async function restoreWallet ( await customWorld.waitForElement(pickUpCurrencyDialog); await customWorld.click(getCurrencyButton('cardano')); customWorld.webDriverLogger.info(`Step:restoreWallet: Selected currency "cardano"`); - await customWorld.waitForElement(walletRestoreDialog); + await customWorld.waitForElement(walletRestoreOptionDialog); await customWorld.click(restoreNormalWallet); customWorld.webDriverLogger.info(`Step:restoreWallet: Selected 15-word wallet`); From 3f365ee9e2ad0696ed1be14bb4269a9215354826 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 19 Oct 2022 19:38:06 +0300 Subject: [PATCH 130/199] rearranged test-cases --- .../features/wallet-restoration.feature | 210 +++++++++--------- 1 file changed, 105 insertions(+), 105 deletions(-) diff --git a/packages/yoroi-extension/features/wallet-restoration.feature b/packages/yoroi-extension/features/wallet-restoration.feature index d88cfe87cc..4cdf961fb7 100644 --- a/packages/yoroi-extension/features/wallet-restoration.feature +++ b/packages/yoroi-extension/features/wallet-restoration.feature @@ -26,6 +26,26 @@ Feature: Restore Wallet | Ae2tdPwUPEZAbDBFpgzALfryWbvDtx6H6BMynDxWFuThQthW7HX93yJ3wRS | | Ae2tdPwUPEZGLVbFwK5EnWiFxwWwLjVtV3CNzy7Hu7tB5nqFxS31uGjjhoc | + @it-11 + Scenario: Fail to completely restore a wallet with addresses generated not following gap from BIP44 protocol (IT-11) + When I click the restore button for cardano + Then I select Byron-era 15-word wallet + And I enter the name "Restored Wallet" + And I enter the recovery phrase: + | recoveryPhrase | + | grace saddle snake vocal amateur coin inside ginger leopard place liar patrol usual joy around | + And I enter the restored wallet password: + | password | repeatedPassword | + | asdfasdfasdf | asdfasdfasdf | + And I click the "Restore Wallet" button + Then I should see a plate HNHT-5379 + Then I click the next button + Then I should see the opened wallet with name "Restored Wallet" + And I go to the receive screen + And I should see the addresses exactly list them + | address | + | Ae2tdPwUPEZLzYQaqFk1U9VWBeY9AfQ2hKBWZjxtfwWVE46sy6u5ZZAeFu1 | + @it-13 Scenario: Mnemonic words can be cleared by pressing "x" sign for each word on wallet restoration screen (IT-13) When I click the restore button for cardano @@ -35,6 +55,91 @@ Feature: Restore Wallet | recoveryPhrase | | eight country switch draw meat scout mystery blade tip drift useless good keep usage title | Then I delete recovery phrase by clicking "x" signs + + @it-26 + Scenario: Wallet can't be restored without entering password (IT-26) + And I click the restore button for cardano + Then I select Byron-era 15-word wallet + And I enter the name "Restored Wallet" + And I enter the recovery phrase: + | recoveryPhrase | + | remind style lunch result accuse upgrade atom eight limit glance frequent eternal fashion borrow monster | + And I enter the restored wallet password: + | password | repeatedPassword | + | asdfasdfasdf | asdfasdfasdf | + And I clear the restored wallet password asdfasdfasdf + Then I see the submit button is disabled + And I should stay in the restore wallet dialog + + @it-70 + Scenario Outline: Wallet restoration Recovery Phrase test (IT-70) + And I click the restore button for cardano + Then I select Byron-era 15-word wallet + And I enter the name "Restored Wallet" + And I enter the recovery phrase: + | recoveryPhrase | + | | + And I enter the restored wallet password: + | password | repeatedPassword | + | asdfasdfasdf | asdfasdfasdf | + Then I see the submit button is disabled + And I should stay in the restore wallet dialog + And I should see an "Invalid recovery phrase" error message: + | message | + | wallet.restore.dialog.form.errors.invalidRecoveryPhrase | + Examples: + | recoveryPhrase | | + | atom remind style monster lunch result upgrade fashion eight limit glance frequent eternal borrow accuse | invalid word order | + + @it-71 + Scenario Outline: Ensure user can not add more than 15 words to the Yoroi Wallet Recovery Phrase (IT-71) + And I click the restore button for cardano + Then I select Byron-era 15-word wallet + And I enter the name "Restored Wallet" + And I can't enter more then 15 words from the recovery phrase: + | recoveryPhrase | + | | + Then I don't see last word of in recovery phrase field + Examples: + | recoveryPhrase | | + | remind style lunch result accuse upgrade atom eight limit glance frequent eternal fashion borrow monster galaxy | 16-words phrase | + + @it-72 + Scenario: Restoring a paper wallet (IT-72) + When I click the restore paper wallet button + And I enter the name "Restored Wallet" + And I enter the recovery phrase: + | recoveryPhrase | + | mushroom expose slogan wagon uphold train absurd fix snake unable rescue curious escape member resource garbage enemy champion airport matrix year | + And I enter the paper wallet password "cool password" + And I enter the restored wallet password: + | password | repeatedPassword | + | asdfasdfasdf | asdfasdfasdf | + And I click the "Restore Wallet" button + Then I should see a plate KOTZ-1730 + Then I click the next button + Then I should see the opened wallet with name "Restored Wallet" + And I go to the receive screen + And I should see the addresses exactly list them + | address | + | Ae2tdPwUPEZF4q7tzeXnofsXLF3yCW7mwbFVxucwoXBrfUCGXJ9yHWzwVm8 | + | Ae2tdPwUPEZ7TQpzbJZCbA5BjW4zWYFn47jKo43ouvfe4EABoCfvEjwYvJr | + + @it-73 + Scenario Outline: Wallet restoration Recovery Phrase with less than 15 words (IT-73) + And I click the restore button for cardano + Then I select Byron-era 15-word wallet + And I enter the name "Restored Wallet" + And I enter the recovery phrase: + | recoveryPhrase | + | | + Then I should see an "1 words left" error message: + | message | + | wallet.restore.dialog.form.errors.shortRecoveryPhrase | + + Examples: + | recoveryPhrase | | + | remind style lunch result accuse upgrade atom eight limit glance frequent eternal fashion borrow | 14-words phrase | @it-86 Scenario: Successfully restoring a simple wallet (IT-86) @@ -84,111 +189,6 @@ Feature: Restore Wallet | Ae2tdPwUPEZBdh5hX9QMWCeiihXf3onFAgx6KzKBtm7nj4wwyN8eoroTWqF | | Ae2tdPwUPEYzErSRwThtfVfBbhM87NCXDwkGHRqSYJcRVP4GS8Lgx3AxAXd | - @it-11 - Scenario: Fail to completely restore a wallet with addresses generated not following gap from BIP44 protocol (IT-11) - When I click the restore button for cardano - Then I select Byron-era 15-word wallet - And I enter the name "Restored Wallet" - And I enter the recovery phrase: - | recoveryPhrase | - | grace saddle snake vocal amateur coin inside ginger leopard place liar patrol usual joy around | - And I enter the restored wallet password: - | password | repeatedPassword | - | asdfasdfasdf | asdfasdfasdf | - And I click the "Restore Wallet" button - Then I should see a plate HNHT-5379 - Then I click the next button - Then I should see the opened wallet with name "Restored Wallet" - And I go to the receive screen - And I should see the addresses exactly list them - | address | - | Ae2tdPwUPEZLzYQaqFk1U9VWBeY9AfQ2hKBWZjxtfwWVE46sy6u5ZZAeFu1 | - - @it-26 - Scenario: Wallet can't be restored without entering password (IT-26) - And I click the restore button for cardano - Then I select Byron-era 15-word wallet - And I enter the name "Restored Wallet" - And I enter the recovery phrase: - | recoveryPhrase | - | remind style lunch result accuse upgrade atom eight limit glance frequent eternal fashion borrow monster | - And I enter the restored wallet password: - | password | repeatedPassword | - | asdfasdfasdf | asdfasdfasdf | - And I clear the restored wallet password asdfasdfasdf - Then I see the submit button is disabled - And I should stay in the restore wallet dialog - - @it-70 - Scenario Outline: Wallet restoration Recovery Phrase test (IT-70) - And I click the restore button for cardano - Then I select Byron-era 15-word wallet - And I enter the name "Restored Wallet" - And I enter the recovery phrase: - | recoveryPhrase | - | | - And I enter the restored wallet password: - | password | repeatedPassword | - | asdfasdfasdf | asdfasdfasdf | - Then I see the submit button is disabled - And I should stay in the restore wallet dialog - And I should see an "Invalid recovery phrase" error message: - | message | - | wallet.restore.dialog.form.errors.invalidRecoveryPhrase | - Examples: - | recoveryPhrase | | - | atom remind style monster lunch result upgrade fashion eight limit glance frequent eternal borrow accuse | invalid word order | - - @it-71 - Scenario Outline: Ensure user can not add more than 15 words to the Yoroi Wallet Recovery Phrase (IT-71) - And I click the restore button for cardano - Then I select Byron-era 15-word wallet - And I enter the name "Restored Wallet" - And I can't enter more then 15 words from the recovery phrase: - | recoveryPhrase | - | | - Then I don't see last word of in recovery phrase field - Examples: - | recoveryPhrase | | - | remind style lunch result accuse upgrade atom eight limit glance frequent eternal fashion borrow monster galaxy | 16-words phrase | - - @it-72 - Scenario: Restoring a paper wallet (IT-72) - When I click the restore paper wallet button - And I enter the name "Restored Wallet" - And I enter the recovery phrase: - | recoveryPhrase | - | mushroom expose slogan wagon uphold train absurd fix snake unable rescue curious escape member resource garbage enemy champion airport matrix year | - And I enter the paper wallet password "cool password" - And I enter the restored wallet password: - | password | repeatedPassword | - | asdfasdfasdf | asdfasdfasdf | - And I click the "Restore Wallet" button - Then I should see a plate KOTZ-1730 - Then I click the next button - Then I should see the opened wallet with name "Restored Wallet" - And I go to the receive screen - And I should see the addresses exactly list them - | address | - | Ae2tdPwUPEZF4q7tzeXnofsXLF3yCW7mwbFVxucwoXBrfUCGXJ9yHWzwVm8 | - | Ae2tdPwUPEZ7TQpzbJZCbA5BjW4zWYFn47jKo43ouvfe4EABoCfvEjwYvJr | - - @it-73 - Scenario Outline: Wallet restoration Recovery Phrase with less than 15 words (IT-73) - And I click the restore button for cardano - Then I select Byron-era 15-word wallet - And I enter the name "Restored Wallet" - And I enter the recovery phrase: - | recoveryPhrase | - | | - Then I should see an "1 words left" error message: - | message | - | wallet.restore.dialog.form.errors.shortRecoveryPhrase | - - Examples: - | recoveryPhrase | | - | remind style lunch result accuse upgrade atom eight limit glance frequent eternal fashion borrow | 14-words phrase | - @it-95 Scenario: Create & delete (1 wallet) (IT-95) # copy the DB From 1b280f0383901f06a6f9c1319bf9e34cb1103cf5 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 13:35:28 +0300 Subject: [PATCH 131/199] updated locators --- .../features/pages/walletSendPage.js | 15 ++++++++++--- .../step_definitions/transactions-steps.js | 22 +++++++++---------- .../support/helpers/common-constants.js | 1 + .../support/helpers/transfer-helpers.js | 2 +- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js index 50e0d93fd5..14d1f5ed97 100644 --- a/packages/yoroi-extension/features/pages/walletSendPage.js +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -4,6 +4,12 @@ import { truncateToken } from '../../app/utils/formatters'; import { By } from 'selenium-webdriver'; import type { LocatorObject } from '../support/webdriver'; +// Modern theme. Old UI +export const sendInputDialogFeesText: LocatorObject = { + locator: '//div[@class="WalletSendForm_amountInput"]/div/p', + method: 'xpath' +}; + export const assetSelector: LocatorObject = { locator: '.WalletSendForm_component .SimpleInput_input', method: 'css', @@ -54,9 +60,12 @@ export const nextButton: LocatorObject = { method: 'css', }; export const invalidAddressError: LocatorObject = { - locator: '.receiver .SimpleInput_errored', - method: 'css', + // [starts-with(@id, "receiver") and contains(@id, "-helper-text")] + locator: '//p[starts-with(@id, "receiver") and contains(@id, "-helper-text")]', + method: 'xpath', }; + +export const invalidAddressErrorMessage = 'Invalid address. Please retype.'; export const notEnoughAdaError: LocatorObject = { locator: '.FormFieldOverridesClassic_error', method: 'css', @@ -72,7 +81,7 @@ export const sendMoneyConfirmationDialog: LocatorObject = { }; export const submitButton: LocatorObject = { locator: '.confirmButton', method: 'css' }; export const disabledSubmitButton: LocatorObject = { - locator: '.primary.SimpleButton_disabled', + locator: '.MuiButton-primary.Mui-disabled', method: 'css', }; diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index c4b97fa020..27e30c58e7 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -1,7 +1,6 @@ // @flow import { Given, When, Then } from 'cucumber'; -import { By } from 'selenium-webdriver'; import { expect } from 'chai'; import i18n from '../support/helpers/i18n-helpers'; import { addTransaction, generateTransaction } from '../mock-chain/mockCardanoImporter'; @@ -17,6 +16,7 @@ import { disabledSubmitButton, getTokenLocator, invalidAddressError, + invalidAddressErrorMessage, nextButton, notEnoughAdaError, receiverInput, @@ -24,6 +24,7 @@ import { selectSendingAmountDropDown, sendAllCheckbox, sendAllItem, + sendInputDialogFeesText, sendConfirmationDialogAddressToText, sendConfirmationDialogAmountText, sendConfirmationDialogError, @@ -39,7 +40,8 @@ import { sendTab } from '../pages/walletPage'; import { walletPasswordInput } from '../pages/restoreWalletPage'; import { delegationTxDialogError } from '../pages/walletDelegationPage'; import { unmangleButton } from '../pages/walletReceivePage'; -import { halfSecond, oneMinute } from '../support/helpers/common-constants'; +import { fiveSeconds, halfSecond, oneMinute } from '../support/helpers/common-constants'; +import { stripZerosFromEnd } from '../support/helpers/transfer-helpers'; const filterInputByBrowser = async (customWorld: any, inputData: any): Promise => { const browserName = await customWorld.getBrowser(); @@ -99,7 +101,10 @@ When(/^I see CONFIRM TRANSACTION Pop up:$/, async function (table) { await this.waitUntilText(sendConfirmationDialogAddressToText, truncateAddress(fields.address)); await this.waitUntilContainsText(sendConfirmationDialogFeesText, fields.fee); - await this.waitUntilContainsText(sendConfirmationDialogAmountText, fields.amount); + await this.waitUntilContainsText( + sendConfirmationDialogAmountText, + stripZerosFromEnd(fields.amount) + ); const network = networks.CardanoMainnet; const assetInfo = defaultAssets.filter(asset => asset.NetworkId === network.NetworkId)[0]; @@ -123,14 +128,7 @@ When(/^I fill the receiver as "([^"]*)"$/, async function (receiver) { }); When(/^The transaction fees are "([^"]*)"$/, async function (fee) { - const result = await this.customWaiter(async () => { - const messageElement = await this.driver - .findElement(By.css('.WalletSendForm_amountInput')) - .findElement(By.xpath('//p')); - const messageText = await messageElement.getText(); - return messageText === `+ ${fee} of fees`; - }); - expect(result).to.be.true; + await this.waitUntilText(sendInputDialogFeesText, `+ ${fee} of fees`, fiveSeconds); }); When(/^I click on the next button in the wallet send form$/, async function () { @@ -189,6 +187,8 @@ Then(/^Revamp. I should see the summary screen$/, async function () { Then(/^I should see an invalid address error$/, async function () { await this.waitForElement(invalidAddressError); + const errorMessage = await this.getText(invalidAddressError); + expect(errorMessage).to.be.equal(invalidAddressErrorMessage, 'The received error is wrong'); }); Then(/^I should see a not enough ada error$/, async function () { diff --git a/packages/yoroi-extension/features/support/helpers/common-constants.js b/packages/yoroi-extension/features/support/helpers/common-constants.js index 1751305880..af8cfda02e 100644 --- a/packages/yoroi-extension/features/support/helpers/common-constants.js +++ b/packages/yoroi-extension/features/support/helpers/common-constants.js @@ -16,6 +16,7 @@ export const txSuccessfulStatuses = ['high', 'medium', 'low']; export const halfSecond = 500; export const oneSecond = 1000; export const defaultRepeatPeriod = oneSecond; +export const fiveSeconds = 5 * oneSecond; export const defaultWaitTimeout = 10 * oneSecond; export const quarterMinute = 15 * oneSecond; export const halfMinute = 30 * oneSecond; diff --git a/packages/yoroi-extension/features/support/helpers/transfer-helpers.js b/packages/yoroi-extension/features/support/helpers/transfer-helpers.js index fc5245098e..2e942d9de8 100644 --- a/packages/yoroi-extension/features/support/helpers/transfer-helpers.js +++ b/packages/yoroi-extension/features/support/helpers/transfer-helpers.js @@ -20,7 +20,7 @@ type WithdrawSourceType = {| recoveredBalance: string | number, |}; -function stripZerosFromEnd(inputNumber: string) { +export function stripZerosFromEnd(inputNumber: string): string { const inputLength = inputNumber.length; const inputArray = inputNumber.split(''); for (let i = inputLength - 1; i >= 0; i--) { From 01c0f5558673cb1fcdd19437f6a2ee3b2be61a18 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 13:35:39 +0300 Subject: [PATCH 132/199] changed fee --- packages/yoroi-extension/features/transactions.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/features/transactions.feature b/packages/yoroi-extension/features/transactions.feature index 141e3e53fa..81674f9c10 100644 --- a/packages/yoroi-extension/features/transactions.feature +++ b/packages/yoroi-extension/features/transactions.feature @@ -61,7 +61,7 @@ Feature: Send transaction And I fill the form: | address | amount | |
    | | - And The transaction fees are "0.640000" + And The transaction fees are "" And I click on the next button in the wallet send form And I see CONFIRM TRANSACTION Pop up: | address | amount |fee | @@ -69,7 +69,7 @@ Feature: Send transaction Examples: | address | amount |fee | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 |0.640000 | + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 |0.199117 | @it-46 Scenario: User can't send funds to the invalid address (IT-46) From d2f9959c7a1957fd00e61a2a4b01563fb753b4a9 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 13:54:49 +0300 Subject: [PATCH 133/199] updated errors locators --- .../yoroi-extension/features/pages/walletSendPage.js | 8 +++----- .../features/step_definitions/transactions-steps.js | 12 ++++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js index 14d1f5ed97..7dc71db661 100644 --- a/packages/yoroi-extension/features/pages/walletSendPage.js +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -60,15 +60,13 @@ export const nextButton: LocatorObject = { method: 'css', }; export const invalidAddressError: LocatorObject = { - // [starts-with(@id, "receiver") and contains(@id, "-helper-text")] locator: '//p[starts-with(@id, "receiver") and contains(@id, "-helper-text")]', method: 'xpath', }; -export const invalidAddressErrorMessage = 'Invalid address. Please retype.'; -export const notEnoughAdaError: LocatorObject = { - locator: '.FormFieldOverridesClassic_error', - method: 'css', +export const amountError: LocatorObject = { + locator: '//p[starts-with(@id, "amount") and contains(@id, "-helper-text")]', + method: 'xpath', }; export const sendAllCheckbox: LocatorObject = { diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index 27e30c58e7..bdaf7987d1 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -16,9 +16,8 @@ import { disabledSubmitButton, getTokenLocator, invalidAddressError, - invalidAddressErrorMessage, nextButton, - notEnoughAdaError, + amountError, receiverInput, selectAssetDropDown, selectSendingAmountDropDown, @@ -186,16 +185,17 @@ Then(/^Revamp. I should see the summary screen$/, async function () { }); Then(/^I should see an invalid address error$/, async function () { - await this.waitForElement(invalidAddressError); - const errorMessage = await this.getText(invalidAddressError); - expect(errorMessage).to.be.equal(invalidAddressErrorMessage, 'The received error is wrong'); + const errorMessage = await i18n.formatMessage(this.driver, { + id: 'wallet.send.form.errors.invalidAddress', + }); + await this.waitUntilText(invalidAddressError, errorMessage); }); Then(/^I should see a not enough ada error$/, async function () { const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.NotEnoughMoneyToSendError', }); - await this.waitUntilText(notEnoughAdaError, errorMessage); + await this.waitUntilText(amountError, errorMessage); }); Then(/^I should not be able to submit$/, async function () { From 17ddead19fc53c0a3c3ddd4632486251be093e3d Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 14:49:24 +0300 Subject: [PATCH 134/199] rearranged steps in the scenario. Changed the expected fee --- packages/yoroi-extension/features/transactions.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/features/transactions.feature b/packages/yoroi-extension/features/transactions.feature index 81674f9c10..70c8d9fc68 100644 --- a/packages/yoroi-extension/features/transactions.feature +++ b/packages/yoroi-extension/features/transactions.feature @@ -98,10 +98,10 @@ Feature: Send transaction Given There is a Byron wallet stored named many-tx-wallet And I have a wallet with funds When I go to the send transaction screen - And I open the amount dropdown and select send all And I fill the address of the form: | address | | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | + And I open the amount dropdown and select send all And The transaction fees are "" And I click on the next button in the wallet send form And I see send money confirmation dialog @@ -116,7 +116,7 @@ Feature: Send transaction Examples: | fee | expectedTx | - | 0.209193 | "g6QAiYJYILcTzA1jEGw4BrGnB3zDeilPzKDkefJqrGTlHgmugI11AIJYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAIJYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAIJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI11AIJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI11AYJYIAoHNmmEX+pK6DzUQYoLT9VmEAl6iWAagWtYkfZn40lsAIJYIAoHNmmEX+pK6DzUQYoLT9VmEAl6iWAagWtYkfZn40lsAQGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoALENMAhoAAzEpAxoR/pTNoQKGhFggXnGsSYUyhwTHwB1G9f7mkd9iHoKwNvA477DkhDTXmX5YQOgqSLhR8sUADEtWnvr6NjvmTSgZecfPXL4Rbu7TIp2UmflUqzzfx1vZ3J9wyB/h6smfq8mlqlqNVyNfO6j07A5YIO23hdKX7dxt3V0Cchr8MLvy1UwjID2hIegQ0F2VF6vHQaCEWCCPBkfbIHjkWa3BQgOfZnSx2uNRXSeHf/3Dr6sioCh8qlhAoi4s6bvzxVwn4Gkdsyewl2mPghpInwoRfQnmQIbGPcI1JoQpB64cB1cd9+f/w+uAt/t2aSU8DSFgkRkWddj9A1ggRYcsb5A2HZ+Idr1h/VYzDe7f0Gt4E0SVRIi2VqWKcA5BoIRYIOx7+AYOSniTUHVqDkzhtFF7b1HM/9BBkw6++m8Q+/SzWEDSvpZpGjk74C0E4gieWGRuFWhdnP4640l259qPRUzr5TnkUhc5BRq/ubxVC6LBqDd11PZa/g8XWGoFUJiY+toAWCBWM2zqwExuk/gcTouvBpcmhtN2NEKb5aluF5K/hwZh2EGghFggzHvtZR+ntErMZIIgbq2uQ2B/jHt0TTIZ2j1JzMH2HTtYQOCCXW2Lo0iUC+HsPqg/MgSbR3Rb+Z/WLxDhQ4t9qeZYnoXUDPOZWMOz9ToWLibo+75g5IzTK+CNWtrgM5BtmAdYIFiANkZLmGZ3Oe+LI3Q8iACb9dHcrlZN4L/sajTg+7+/QaCEWCCrz2wieKqlEL0JsQicx3naLrAb0qgH4COJ/AE3AZvCd1hATLVkXVaSCrYGXA3jOCO9JSZiqhyRYLG1IZaSVETBS9Xb9ovg+kCCX8V43NxNuwLVQ5udSiJKv5Sbf9CUhpfyAVggXsJ5mvWuSD0y///s18T45fzDjpah0vqXB3erntI0INFBoIRYILlNNm5mIlb6YPjq4n8gjDChi+2y90adZ81HgfMcpj8GWEDjpBRrBC2WEwVLLgI0qgXRYmxNAtjRcPdEaFKTugqQ0/vup0jP1r4wMq6rqkqMWOWdaodtClZs3KBIW1cXWv0FWCCux11USi/M3OmzDhjc8euPOY/1uQF34sJFYQk85C/rpUGg9g==" | + | 0.209237 | "g6QAiYJYILcTzA1jEGw4BrGnB3zDeilPzKDkefJqrGTlHgmugI11AIJYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAIJYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAIJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI11AIJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI11AYJYIAoHNmmEX+pK6DzUQYoLT9VmEAl6iWAagWtYkfZn40lsAIJYIAoHNmmEX+pK6DzUQYoLT9VmEAl6iWAagWtYkfZn40lsAQGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoALENMAhoAAzEpAxoR/pTNoQKGhFggXnGsSYUyhwTHwB1G9f7mkd9iHoKwNvA477DkhDTXmX5YQOgqSLhR8sUADEtWnvr6NjvmTSgZecfPXL4Rbu7TIp2UmflUqzzfx1vZ3J9wyB/h6smfq8mlqlqNVyNfO6j07A5YIO23hdKX7dxt3V0Cchr8MLvy1UwjID2hIegQ0F2VF6vHQaCEWCCPBkfbIHjkWa3BQgOfZnSx2uNRXSeHf/3Dr6sioCh8qlhAoi4s6bvzxVwn4Gkdsyewl2mPghpInwoRfQnmQIbGPcI1JoQpB64cB1cd9+f/w+uAt/t2aSU8DSFgkRkWddj9A1ggRYcsb5A2HZ+Idr1h/VYzDe7f0Gt4E0SVRIi2VqWKcA5BoIRYIOx7+AYOSniTUHVqDkzhtFF7b1HM/9BBkw6++m8Q+/SzWEDSvpZpGjk74C0E4gieWGRuFWhdnP4640l259qPRUzr5TnkUhc5BRq/ubxVC6LBqDd11PZa/g8XWGoFUJiY+toAWCBWM2zqwExuk/gcTouvBpcmhtN2NEKb5aluF5K/hwZh2EGghFggzHvtZR+ntErMZIIgbq2uQ2B/jHt0TTIZ2j1JzMH2HTtYQOCCXW2Lo0iUC+HsPqg/MgSbR3Rb+Z/WLxDhQ4t9qeZYnoXUDPOZWMOz9ToWLibo+75g5IzTK+CNWtrgM5BtmAdYIFiANkZLmGZ3Oe+LI3Q8iACb9dHcrlZN4L/sajTg+7+/QaCEWCCrz2wieKqlEL0JsQicx3naLrAb0qgH4COJ/AE3AZvCd1hATLVkXVaSCrYGXA3jOCO9JSZiqhyRYLG1IZaSVETBS9Xb9ovg+kCCX8V43NxNuwLVQ5udSiJKv5Sbf9CUhpfyAVggXsJ5mvWuSD0y///s18T45fzDjpah0vqXB3erntI0INFBoIRYILlNNm5mIlb6YPjq4n8gjDChi+2y90adZ81HgfMcpj8GWEDjpBRrBC2WEwVLLgI0qgXRYmxNAtjRcPdEaFKTugqQ0/vup0jP1r4wMq6rqkqMWOWdaodtClZs3KBIW1cXWv0FWCCux11USi/M3OmzDhjc8euPOY/1uQF34sJFYQk85C/rpUGg9g==" | @invalidWitnessTest @it-20 Scenario: Sending a Tx and receiving from the server an invalid signature error (IT-20) From cf6420d4f339eb1ab9c06623664fdf25fac46145 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 15:17:48 +0300 Subject: [PATCH 135/199] created the special step for the ERGO --- .../step_definitions/transactions-steps.js | 33 +++++++++++++------ .../features/transactions.feature | 2 +- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index bdaf7987d1..551a489eac 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -35,7 +35,7 @@ import { transactionPageButton, warningBox, } from '../pages/walletSendPage'; -import { sendTab } from '../pages/walletPage'; +import { navDetailsAmount, sendTab } from '../pages/walletPage'; import { walletPasswordInput } from '../pages/restoreWalletPage'; import { delegationTxDialogError } from '../pages/walletDelegationPage'; import { unmangleButton } from '../pages/walletReceivePage'; @@ -54,16 +54,29 @@ const filterInputByBrowser = async (customWorld: any, inputData: any): Promise => { + const balanceTextElement = await customWorld.findElement(navDetailsAmount); + const balanceText = await balanceTextElement.getText(); + const [balance, ] = balanceText.split(' '); + expect(parseFloat(balance), 'The wallet is empty').to.be.above(0); +}; + Given(/^I have a wallet with funds$/, async function () { await this.waitUntilContainsText( - { locator: '.NavWalletDetails_amount', method: 'css' }, + navDetailsAmount, 'ADA', oneMinute ); - const balanceTextElement = await this.findElement({ locator: '.NavWalletDetails_amount', method: 'css' }); - const balanceText = await balanceTextElement.getText(); - const [balance, ] = balanceText.split(' '); - expect(parseFloat(balance), 'The wallet is empty').to.be.above(0); + await walletIsEmpty(this); +}); + +Given(/^I have an ERGO wallet with funds$/, async function () { + await this.waitUntilContainsText( + navDetailsAmount, + 'ERG', + oneMinute + ); + await walletIsEmpty(this); }); When(/^I go to the send transaction screen$/, async function () { @@ -99,7 +112,7 @@ When(/^I see CONFIRM TRANSACTION Pop up:$/, async function (table) { const total = parseFloat(fields.amount) + parseFloat(fields.fee); await this.waitUntilText(sendConfirmationDialogAddressToText, truncateAddress(fields.address)); - await this.waitUntilContainsText(sendConfirmationDialogFeesText, fields.fee); + await this.waitUntilContainsText(sendConfirmationDialogFeesText, stripZerosFromEnd(fields.fee)); await this.waitUntilContainsText( sendConfirmationDialogAmountText, stripZerosFromEnd(fields.amount) @@ -107,10 +120,10 @@ When(/^I see CONFIRM TRANSACTION Pop up:$/, async function (table) { const network = networks.CardanoMainnet; const assetInfo = defaultAssets.filter(asset => asset.NetworkId === network.NetworkId)[0]; - const decimalPlaces = assetInfo.Metadata.numberOfDecimals; + const totalWithDecimals = total.toFixed(assetInfo.Metadata.numberOfDecimals); await this.waitUntilContainsText( sendConfirmationDialogTotalAmountText, - total.toFixed(decimalPlaces) + stripZerosFromEnd(totalWithDecimals) ); }); @@ -127,7 +140,7 @@ When(/^I fill the receiver as "([^"]*)"$/, async function (receiver) { }); When(/^The transaction fees are "([^"]*)"$/, async function (fee) { - await this.waitUntilText(sendInputDialogFeesText, `+ ${fee} of fees`, fiveSeconds); + await this.waitUntilText(sendInputDialogFeesText, `+ ${stripZerosFromEnd(fee)} of fees`, fiveSeconds); }); When(/^I click on the next button in the wallet send form$/, async function () { diff --git a/packages/yoroi-extension/features/transactions.feature b/packages/yoroi-extension/features/transactions.feature index 70c8d9fc68..5a31d55d2f 100644 --- a/packages/yoroi-extension/features/transactions.feature +++ b/packages/yoroi-extension/features/transactions.feature @@ -280,7 +280,7 @@ Feature: Send transaction @it-162 Scenario Outline: Send from an ergo wallet (IT-162) Given There is an Ergo wallet stored named ergo-simple-wallet - And I have a wallet with funds + And I have an ERGO wallet with funds When I go to the send transaction screen And I fill the form: | address | amount | From 7f25682f87b5608e4b768f45da3391a916bbaf2f Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 15:19:50 +0300 Subject: [PATCH 136/199] rearranged steps in the scenario --- packages/yoroi-extension/features/transactions.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/transactions.feature b/packages/yoroi-extension/features/transactions.feature index 5a31d55d2f..88dc12db4f 100644 --- a/packages/yoroi-extension/features/transactions.feature +++ b/packages/yoroi-extension/features/transactions.feature @@ -319,10 +319,10 @@ Feature: Send transaction Then I click the next button Then I should see the opened wallet with name "Restored Wallet" When I go to the send transaction screen - And I open the amount dropdown and select send all And I fill the address of the form: | address | | 9guxMsa2S1Z4xzr5JHUHZesznThjZ4BMM9Ra5Lfx2E9duAnxEmv | + And I open the amount dropdown and select send all And The transaction fees are "0.001100000" And I click on the next button in the wallet send form And I see send money confirmation dialog From 22e434a1633eed34afc2648854198573c7ac6b2b Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 15:51:47 +0300 Subject: [PATCH 137/199] fixed IT-165 --- .../yoroi-extension/features/pages/walletReceivePage.js | 6 +++--- .../features/step_definitions/transactions-steps.js | 4 ++-- .../features/support/helpers/transfer-helpers.js | 5 +++-- packages/yoroi-extension/features/transactions.feature | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletReceivePage.js b/packages/yoroi-extension/features/pages/walletReceivePage.js index 76d7742078..d859cf4ad1 100644 --- a/packages/yoroi-extension/features/pages/walletReceivePage.js +++ b/packages/yoroi-extension/features/pages/walletReceivePage.js @@ -84,9 +84,9 @@ export const verifyAddressHWButton: LocatorObject = { locator: '.VerifyAddressDialog_component .primary', method: 'css', }; -export const unmangleButton: LocatorObject = { - locator: '.MangledHeader_submitButton ', - method: 'css', +export const correctDelegationButton: LocatorObject = { + locator: '//div[@class="WarningHeader_component"]/button[contains(text(), "Correct delegation")]', + method: 'xpath', }; export const generateUriIcon: LocatorObject = { diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index 551a489eac..bb2974e18d 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -38,7 +38,7 @@ import { import { navDetailsAmount, sendTab } from '../pages/walletPage'; import { walletPasswordInput } from '../pages/restoreWalletPage'; import { delegationTxDialogError } from '../pages/walletDelegationPage'; -import { unmangleButton } from '../pages/walletReceivePage'; +import { correctDelegationButton } from '../pages/walletReceivePage'; import { fiveSeconds, halfSecond, oneMinute } from '../support/helpers/common-constants'; import { stripZerosFromEnd } from '../support/helpers/transfer-helpers'; @@ -255,7 +255,7 @@ Then(/^I should see no warning block$/, async function () { }); When(/^I click on the unmangle button$/, async function () { - await this.click(unmangleButton); + await this.click(correctDelegationButton); }); When(/^I open the token selection dropdown$/, async function () { diff --git a/packages/yoroi-extension/features/support/helpers/transfer-helpers.js b/packages/yoroi-extension/features/support/helpers/transfer-helpers.js index 2e942d9de8..21fbb07764 100644 --- a/packages/yoroi-extension/features/support/helpers/transfer-helpers.js +++ b/packages/yoroi-extension/features/support/helpers/transfer-helpers.js @@ -7,6 +7,7 @@ import { defaultAssets, } from '../../../app/api/ada/lib/storage/database/prepackaged/networks'; import { getTokenName } from '../../../app/stores/stateless/tokenHelpers'; +import { amountField, totalAmountField } from '../../pages/confirmTransactionPage'; type TransferSourceType = Array<{| fromAddress: string, @@ -79,7 +80,7 @@ export async function checkTotalAmountIsCorrect( const totalAmountFormatted = `${totalAmount .dividedBy(amountPerUnit) .toFormat(decimalPlaces)} ${ticker}`; - await world.waitUntilText({ locator: '.TransferSummaryPage_amount', method: 'css' }, totalAmountFormatted); + await world.waitUntilText(amountField, totalAmountFormatted); } export async function checkFinalBalanceIsCorrect( @@ -100,5 +101,5 @@ export async function checkFinalBalanceIsCorrect( const ticker = getTokenName(assetInfo); const finalBalance = `${finalAmount} ${ticker}`; - await world.waitUntilText({ locator: '.TransferSummaryPage_totalAmount', method: 'css' }, finalBalance); + await world.waitUntilText(totalAmountField, finalBalance); } diff --git a/packages/yoroi-extension/features/transactions.feature b/packages/yoroi-extension/features/transactions.feature index 88dc12db4f..a0bd6c51cc 100644 --- a/packages/yoroi-extension/features/transactions.feature +++ b/packages/yoroi-extension/features/transactions.feature @@ -376,8 +376,8 @@ Feature: Send transaction | addr1q8sm64ehfue7m7xrlh2zfu4uj9tn3z3yrzfdaly52gs667qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhzdk70 | When I click on the unmangle button Then I should see on the Yoroi transfer summary screen: - | fromAddress | amount | - | addr1q8sm64ehfue7m7xrlh2zfu4uj9tn3z3yrzfdaly52gs667qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhzdk70 | 10000000 | + | fromAddress | recoveredBalance | fees | + | addr1q8sm64ehfue7m7xrlh2zfu4uj9tn3z3yrzfdaly52gs667qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhzdk70 | 10 | 0.165457 | And I enter the wallet password: | password | | asdfasdfasdf | From 8e8a0f768d14850979ec0b51d86e5f3807910ebe Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 15:55:27 +0300 Subject: [PATCH 138/199] using the correct step --- packages/yoroi-extension/features/transactions.feature | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/yoroi-extension/features/transactions.feature b/packages/yoroi-extension/features/transactions.feature index a0bd6c51cc..f09e5575cd 100644 --- a/packages/yoroi-extension/features/transactions.feature +++ b/packages/yoroi-extension/features/transactions.feature @@ -389,12 +389,10 @@ Feature: Send transaction @it-170 Scenario Outline: Can send some of a custom token (IT-170) Given There is an Ergo wallet stored named ergo-token-wallet - And I have a wallet with funds + And I have an ERGO wallet with funds When I go to the send transaction screen - And I open the token selection dropdown And I select token "USD" - And I fill the form: | address | amount | |
    | | From 42f7b92520f6b17981a293ffb77ee578145424f1 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 15:56:57 +0300 Subject: [PATCH 139/199] using the correct step it-171 --- packages/yoroi-extension/features/transactions.feature | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/features/transactions.feature b/packages/yoroi-extension/features/transactions.feature index f09e5575cd..b3d2cd02ea 100644 --- a/packages/yoroi-extension/features/transactions.feature +++ b/packages/yoroi-extension/features/transactions.feature @@ -417,15 +417,14 @@ Feature: Send transaction @it-171 Scenario: Can send all of a custom token (IT-171) Given There is an Ergo wallet stored named ergo-token-wallet - And I have a wallet with funds + And I have an ERGO wallet with funds When I go to the send transaction screen - And I open the token selection dropdown And I select token "USD" - And I open the amount dropdown and select send all And I fill the address of the form: | address | | 9guxMsa2S1Z4xzr5JHUHZesznThjZ4BMM9Ra5Lfx2E9duAnxEmv | + And I open the amount dropdown and select send all And The transaction fees are "0.001100000" And I click on the next button in the wallet send form And I see send money confirmation dialog From b2c991446ac248f4283e3b23dfa13626d3599137 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 20:22:53 +0300 Subject: [PATCH 140/199] fixed it-178 --- .../features/pages/walletSendPage.js | 18 ++++++++++++++++++ .../step_definitions/transactions-steps.js | 9 ++++----- .../features/transactions.feature | 10 ++++------ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js index 7dc71db661..56edd139ae 100644 --- a/packages/yoroi-extension/features/pages/walletSendPage.js +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -4,6 +4,11 @@ import { truncateToken } from '../../app/utils/formatters'; import { By } from 'selenium-webdriver'; import type { LocatorObject } from '../support/webdriver'; +type AmountItem = {| + tokenName: string, + amount: string, +|}; + // Modern theme. Old UI export const sendInputDialogFeesText: LocatorObject = { locator: '//div[@class="WalletSendForm_amountInput"]/div/p', @@ -91,6 +96,19 @@ export const transactionPageButton: LocatorObject = { // Send confirmation Dialog +export const getAmountItems = async (customWorld: any): Promise> => { + const result = []; + const amountElements = await customWorld.findElements(sendConfirmationDialogAmountText); + for (const amountElement of amountElements) { + const [elAmount, elToken] = (await amountElement.getText()).split(' '); + result.push({ + tokenName: elToken.toLowerCase(), + amount: elAmount, + }); + } + return result; +}; + export const sendConfirmationDialogAddressToText: LocatorObject = { locator: '.WalletSendConfirmationDialog_addressTo', method: 'css', diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index bb2974e18d..730d47c534 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -14,6 +14,7 @@ import { walletSummaryBox, walletSummaryComponent } from '../pages/walletTransac import { amountInput, disabledSubmitButton, + getAmountItems, getTokenLocator, invalidAddressError, nextButton, @@ -25,7 +26,6 @@ import { sendAllItem, sendInputDialogFeesText, sendConfirmationDialogAddressToText, - sendConfirmationDialogAmountText, sendConfirmationDialogError, sendConfirmationDialogFeesText, sendConfirmationDialogTotalAmountText, @@ -113,10 +113,9 @@ When(/^I see CONFIRM TRANSACTION Pop up:$/, async function (table) { await this.waitUntilText(sendConfirmationDialogAddressToText, truncateAddress(fields.address)); await this.waitUntilContainsText(sendConfirmationDialogFeesText, stripZerosFromEnd(fields.fee)); - await this.waitUntilContainsText( - sendConfirmationDialogAmountText, - stripZerosFromEnd(fields.amount) - ); + const allItems = await getAmountItems(this); + const adaItem = allItems.filter(item => item.tokenName === 'ada')[0]; + expect(adaItem.amount).to.be.equal(fields.amount); const network = networks.CardanoMainnet; const assetInfo = defaultAssets.filter(asset => asset.NetworkId === network.NetworkId)[0]; diff --git a/packages/yoroi-extension/features/transactions.feature b/packages/yoroi-extension/features/transactions.feature index b3d2cd02ea..097db08552 100644 --- a/packages/yoroi-extension/features/transactions.feature +++ b/packages/yoroi-extension/features/transactions.feature @@ -441,19 +441,17 @@ Feature: Send transaction Given There is a Shelley wallet stored named cardano-token-wallet And I have a wallet with funds When I go to the send transaction screen - And I open the token selection dropdown And I select token "nicoin" - And I fill the form: | address | amount | - |
    | | + |
    | | And The transaction fees are "" And I click on the next button in the wallet send form And I see send money confirmation dialog And I see CONFIRM TRANSACTION Pop up: | address | amount |fee | - |
    | 1.444443 | | + |
    | 1.344798 | | And I enter the wallet password: | password | | asdfasdfasdf | @@ -464,8 +462,8 @@ Feature: Send transaction Then I should see the summary screen Examples: - | address | amount |fee | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 24 |0.171397 | + | address | tokensAmount |fee | + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 24 |0.174301 | @it-179 Scenario: Can send all of a custom Cardano token (IT-179) From 2ad00fec4d0bb2e0d0be35d4fd291bfb541171a7 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 20:26:12 +0300 Subject: [PATCH 141/199] updated steps in the scenario --- packages/yoroi-extension/features/transactions.feature | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/yoroi-extension/features/transactions.feature b/packages/yoroi-extension/features/transactions.feature index 097db08552..f11e842203 100644 --- a/packages/yoroi-extension/features/transactions.feature +++ b/packages/yoroi-extension/features/transactions.feature @@ -470,15 +470,13 @@ Feature: Send transaction Given There is a Shelley wallet stored named cardano-token-wallet And I have a wallet with funds When I go to the send transaction screen - And I open the token selection dropdown And I select token "nicoin" - - And I open the amount dropdown and select send all And I fill the address of the form: | address | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | - And The transaction fees are "0.169637" + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | + And I open the amount dropdown and select send all + And The transaction fees are "0.169593" And I click on the next button in the wallet send form And I see send money confirmation dialog And I enter the wallet password: From 1e94f6501bfb64e082f01d1d70d6ff7d8505281b Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 20 Oct 2022 23:04:58 +0300 Subject: [PATCH 142/199] fixes for the many-tx-wallet --- .../yoroi-extension/features/main-ui.feature | 4 +-- .../mock-chain/mockCardanoImporter.js | 16 +++++----- .../features/pages/daedalusTransferPage.js | 4 +-- .../step_definitions/transactions-steps.js | 9 ++++-- .../features/transactions.feature | 30 +++++++++---------- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/packages/yoroi-extension/features/main-ui.feature b/packages/yoroi-extension/features/main-ui.feature index 00c7062cc7..2e59d0eab8 100644 --- a/packages/yoroi-extension/features/main-ui.feature +++ b/packages/yoroi-extension/features/main-ui.feature @@ -7,7 +7,7 @@ Feature: Main UI Scenario: Restore wallet and get balance with many addresses (IT-81) Given I have completed the basic setup And There is a Byron wallet stored named many-tx-wallet - Then I should see the balance number "3.110005 ADA" + Then I should see the balance number "6.110005 ADA" @it-15 Scenario: Main Screen Tabs Switching (IT-15) @@ -63,7 +63,7 @@ Feature: Main UI When I refresh the page Then I should see my balance hidden When I click on hide balance button - Then I should see the balance number "3.110005 ADA" + Then I should see the balance number "6.110005 ADA" @it-180 Scenario: Ensure user can hide balance (IT-180) diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoImporter.js b/packages/yoroi-extension/features/mock-chain/mockCardanoImporter.js index 2a636606d6..e4549b539f 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoImporter.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoImporter.js @@ -623,7 +623,7 @@ export const generateTransaction = (): {| txHash: distributorTx.hash, id: distributorTx.hash + '4', index: 4, - amount: '1000000', + amount: '2000000', assets: [], }, ], @@ -649,7 +649,7 @@ export const generateTransaction = (): {| ChainDerivations.INTERNAL, 0, ]), - amount: '820000', + amount: '1820000', assets: [], }, ], @@ -677,7 +677,7 @@ export const generateTransaction = (): {| txHash: distributorTx.hash, id: distributorTx.hash + '5', index: 5, - amount: '1000000', + amount: '2000000', assets: [], }, ], @@ -703,7 +703,7 @@ export const generateTransaction = (): {| ChainDerivations.INTERNAL, 1, ]), - amount: '820000', + amount: '1820000', assets: [], }, ], @@ -731,7 +731,7 @@ export const generateTransaction = (): {| txHash: distributorTx.hash, id: distributorTx.hash + '6', index: 6, - amount: '1000000', + amount: '2000000', assets: [], }, ], @@ -757,7 +757,7 @@ export const generateTransaction = (): {| ChainDerivations.INTERNAL, 2, ]), - amount: '820000', + amount: '1820000', assets: [], }, ], @@ -785,7 +785,7 @@ export const generateTransaction = (): {| txHash: distributorTx.hash, id: distributorTx.hash + '7', index: 7, - amount: '1000000', + amount: '2000000', assets: [], }, ], @@ -811,7 +811,7 @@ export const generateTransaction = (): {| ChainDerivations.INTERNAL, 3, ]), - amount: '820000', + amount: '1820000', assets: [], }, ], diff --git a/packages/yoroi-extension/features/pages/daedalusTransferPage.js b/packages/yoroi-extension/features/pages/daedalusTransferPage.js index ec44f968ea..1197577b4f 100644 --- a/packages/yoroi-extension/features/pages/daedalusTransferPage.js +++ b/packages/yoroi-extension/features/pages/daedalusTransferPage.js @@ -7,8 +7,8 @@ export const nextButton: LocatorObject = { method: 'xpath', }; export const backButton: LocatorObject = { - locator: "//button[contains(@label, 'Back')]", - method: 'xpath', + locator: '.secondary', + method: 'css', }; export const formFieldOverridesClassicError: LocatorObject = { locator: '.FormFieldOverridesClassic_error', diff --git a/packages/yoroi-extension/features/step_definitions/transactions-steps.js b/packages/yoroi-extension/features/step_definitions/transactions-steps.js index 730d47c534..fd2169f3f7 100644 --- a/packages/yoroi-extension/features/step_definitions/transactions-steps.js +++ b/packages/yoroi-extension/features/step_definitions/transactions-steps.js @@ -114,8 +114,13 @@ When(/^I see CONFIRM TRANSACTION Pop up:$/, async function (table) { await this.waitUntilText(sendConfirmationDialogAddressToText, truncateAddress(fields.address)); await this.waitUntilContainsText(sendConfirmationDialogFeesText, stripZerosFromEnd(fields.fee)); const allItems = await getAmountItems(this); - const adaItem = allItems.filter(item => item.tokenName === 'ada')[0]; - expect(adaItem.amount).to.be.equal(fields.amount); + let mainCoinItem; + if (fields.isErgo && fields.isErgo === '1'){ + mainCoinItem = allItems.filter(item => item.tokenName === 'erg')[0]; + } else { + mainCoinItem = allItems.filter(item => item.tokenName === 'ada')[0]; + } + expect(mainCoinItem.amount).to.be.equal(stripZerosFromEnd(fields.amount)); const network = networks.CardanoMainnet; const assetInfo = defaultAssets.filter(asset => asset.NetworkId === network.NetworkId)[0]; diff --git a/packages/yoroi-extension/features/transactions.feature b/packages/yoroi-extension/features/transactions.feature index f11e842203..61acdb2bcd 100644 --- a/packages/yoroi-extension/features/transactions.feature +++ b/packages/yoroi-extension/features/transactions.feature @@ -28,8 +28,8 @@ Feature: Send transaction Examples: | amount | fee | expectedTx | - | 1.000000 | 0.640000 | "g6QAgoJYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAQGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoAD0JAAhoACcQAAxoR/pTNoQKChFggjwZH2yB45FmtwUIDn2Z0sdrjUV0nh3/9w6+rIqAofKpYQJnz4URZfjl5AOpaEjMTWdF1lsFXPE+IKC0puBMdBjkWaqk6BIzW8VZ7pu7f8J/Mv8E3lRFiBVX7BnDQNYlT7g1YIEWHLG+QNh2fiHa9Yf1WMw3u39BreBNElUSItlalinAOQaCEWCDse/gGDkp4k1B1ag5M4bRRe29RzP/QQZMOvvpvEPv0s1hASaHptgaRcfgiyLBN80gqUIAueQBKkwSLu9iAd+UIN7PELThmN5ItmVzMeZIevE/7eS7wFX15sax9ap5ihK/1ClggVjNs6sBMbpP4HE6LrwaXJobTdjRCm+WpbheSv4cGYdhBoPY=" | - | 2.000000 | 0.460000 | "g6QAg4JYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI11AQGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoAHoSAAhoABwTgAxoR/pTNoQKDhFggjwZH2yB45FmtwUIDn2Z0sdrjUV0nh3/9w6+rIqAofKpYQLM12LTh1NoxSUm0bbVTQfygHhTVh6bLYXlVC4cnlUPGu45AojcnGEqZTE/+vItV7EvsBBlKTapopZofryrXrA5YIEWHLG+QNh2fiHa9Yf1WMw3u39BreBNElUSItlalinAOQaCEWCDse/gGDkp4k1B1ag5M4bRRe29RzP/QQZMOvvpvEPv0s1hAwPcLKhKTeclUAgVMRh7EHv2F+jv6zQ7fpsQwbSucHpESYz+8QamNsB45zU8ocp5K4WDesTR7nxE7I9lvBc0jAlggVjNs6sBMbpP4HE6LrwaXJobTdjRCm+WpbheSv4cGYdhBoIRYIMx77WUfp7RKzGSCIG6trkNgf4x7dE0yGdo9SczB9h07WEBwOJ1TbOoaNh9Uf+erO5luL5/5sIvddiala/3VB2vFJXbohZnvvrJvQoi5Cc0AthRftjupDgna8dmSt25s7WYFWCBYgDZGS5hmdznviyN0PIgAm/XR3K5WTeC/7Go04Pu/v0Gg9g==" | + | 1.000000 | 0.191505 | "g6QAgoJYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAQGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoAD0JAAhoACcQAAxoR/pTNoQKChFggjwZH2yB45FmtwUIDn2Z0sdrjUV0nh3/9w6+rIqAofKpYQJnz4URZfjl5AOpaEjMTWdF1lsFXPE+IKC0puBMdBjkWaqk6BIzW8VZ7pu7f8J/Mv8E3lRFiBVX7BnDQNYlT7g1YIEWHLG+QNh2fiHa9Yf1WMw3u39BreBNElUSItlalinAOQaCEWCDse/gGDkp4k1B1ag5M4bRRe29RzP/QQZMOvvpvEPv0s1hASaHptgaRcfgiyLBN80gqUIAueQBKkwSLu9iAd+UIN7PELThmN5ItmVzMeZIevE/7eS7wFX15sax9ap5ihK/1ClggVjNs6sBMbpP4HE6LrwaXJobTdjRCm+WpbheSv4cGYdhBoPY=" | + | 2.000000 | 0.199117 | "g6QAg4JYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI11AQGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoAHoSAAhoABwTgAxoR/pTNoQKDhFggjwZH2yB45FmtwUIDn2Z0sdrjUV0nh3/9w6+rIqAofKpYQLM12LTh1NoxSUm0bbVTQfygHhTVh6bLYXlVC4cnlUPGu45AojcnGEqZTE/+vItV7EvsBBlKTapopZofryrXrA5YIEWHLG+QNh2fiHa9Yf1WMw3u39BreBNElUSItlalinAOQaCEWCDse/gGDkp4k1B1ag5M4bRRe29RzP/QQZMOvvpvEPv0s1hAwPcLKhKTeclUAgVMRh7EHv2F+jv6zQ7fpsQwbSucHpESYz+8QamNsB45zU8ocp5K4WDesTR7nxE7I9lvBc0jAlggVjNs6sBMbpP4HE6LrwaXJobTdjRCm+WpbheSv4cGYdhBoIRYIMx77WUfp7RKzGSCIG6trkNgf4x7dE0yGdo9SczB9h07WEBwOJ1TbOoaNh9Uf+erO5luL5/5sIvddiala/3VB2vFJXbohZnvvrJvQoi5Cc0AthRftjupDgna8dmSt25s7WYFWCBYgDZGS5hmdznviyN0PIgAm/XR3K5WTeC/7Go04Pu/v0Gg9g==" | @it-90 Scenario Outline: Spending Password should be case-sensitive [Transaction confirmation] (IT-90) @@ -69,7 +69,7 @@ Feature: Send transaction Examples: | address | amount |fee | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 |0.199117 | + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 |0.191505 | @it-46 Scenario: User can't send funds to the invalid address (IT-46) @@ -126,7 +126,7 @@ Feature: Send transaction And I fill the form: | address | amount | | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 | - And The transaction fees are "0.640000" + And The transaction fees are "0.191505" And I click on the next button in the wallet send form And I see send money confirmation dialog And I enter the wallet password: @@ -143,7 +143,7 @@ Feature: Send transaction And I fill the form: | address | amount | | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 | - And The transaction fees are "0.640000" + And The transaction fees are "0.191505" And I click on the next button in the wallet send form And I see send money confirmation dialog And I enter the wallet password: @@ -186,7 +186,7 @@ Feature: Send transaction And I fill the form: | address | amount | | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 2.000000 | - And The transaction fees are "0.460000" + And The transaction fees are "0.199117" And I click on the next button in the wallet send form And I see send money confirmation dialog Then A successful tx gets sent from my wallet from another client @@ -213,7 +213,7 @@ Feature: Send transaction And I fill the form: | address | amount | | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 | - And The transaction fees are "0.640000" + And The transaction fees are "0.191505" And I click on the next button in the wallet send form And I see send money confirmation dialog And I enter the wallet password: @@ -239,7 +239,7 @@ Feature: Send transaction And I fill the form: | address | amount | | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 2.000000 | - And The transaction fees are "0.460000" + And The transaction fees are "0.199117" Then A pending tx gets sent from my wallet from another client Then I should see a warning block @@ -289,8 +289,8 @@ Feature: Send transaction And I click on the next button in the wallet send form And I see send money confirmation dialog And I see CONFIRM TRANSACTION Pop up: - | address | amount |fee | - |
    | | | + | address | amount |fee | isErgo | + |
    | | | 1 | And I enter the wallet password: | password | | asdfasdfasdf | @@ -301,7 +301,7 @@ Feature: Send transaction Examples: | address | amount |fee | - | 9guxMsa2S1Z4xzr5JHUHZesznThjZ4BMM9Ra5Lfx2E9duAnxEmv | 5.000000000 |0.001100000 | + | 9guxMsa2S1Z4xzr5JHUHZesznThjZ4BMM9Ra5Lfx2E9duAnxEmv | 5.000000000 |0.001100000 | @it-163 Scenario: Send all from an ergo wallet (IT-163) @@ -363,7 +363,7 @@ Feature: Send transaction Examples: | amount | fee | expectedTx | - | 1.000000 | 0.167657 | "g6QAgYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgSugI11FgGCglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoAD0JAglg5Af0wLcvG6+RrU5YfLU0x/8kfm6HbRxOuVhSWZr6F4UOa5bEdhsrcaqLcJ6I+wLsaSX6JnAGqOfdrGgCGxVcCGgACjukDGhH+lM2hAIGCWCAosn+8mv+rxG+osiOOtkzZqx67+DrT7IF+s0fWbhA6bFhAokhEoNLBWrk60+zXT7fcdHwV5A6j6BUyd8x2g52AGEjx6ckVVBpKSUWLfMCYiuz2m6Z+6zV7lMvgR/QL9qFDBvY=" | + | 1.000000 | 0.182001 | "g6QAgYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgSugI11FgGCglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoAD0JAglg5Af0wLcvG6+RrU5YfLU0x/8kfm6HbRxOuVhSWZr6F4UOa5bEdhsrcaqLcJ6I+wLsaSX6JnAGqOfdrGgCGxVcCGgACjukDGhH+lM2hAIGCWCAosn+8mv+rxG+osiOOtkzZqx67+DrT7IF+s0fWbhA6bFhAokhEoNLBWrk60+zXT7fcdHwV5A6j6BUyd8x2g52AGEjx6ckVVBpKSUWLfMCYiuz2m6Z+6zV7lMvgR/QL9qFDBvY=" | @it-165 Scenario: Can receive & unmangle utxo entries (IT-165) @@ -400,8 +400,8 @@ Feature: Send transaction And I click on the next button in the wallet send form And I see send money confirmation dialog And I see CONFIRM TRANSACTION Pop up: - | address | amount |fee | - |
    | 0.010000000 | | + | address | amount |fee | isErgo | + |
    | 0.010000000 | | 1 | And I enter the wallet password: | password | | asdfasdfasdf | @@ -411,7 +411,7 @@ Feature: Send transaction Then I should see the summary screen Examples: - | address | amount |fee | + | address | amount | fee | | 9guxMsa2S1Z4xzr5JHUHZesznThjZ4BMM9Ra5Lfx2E9duAnxEmv | 123 |0.001100000 | @it-171 From f9bf9d86bc527b601084a2743378c557228f441d Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 21 Oct 2022 10:57:50 +0300 Subject: [PATCH 143/199] fixed memo --- packages/yoroi-extension/features/pages/walletSendPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js index 56edd139ae..84495dd34c 100644 --- a/packages/yoroi-extension/features/pages/walletSendPage.js +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -36,7 +36,7 @@ export const getMemoText = async (customWorld: Object): Promise => { return await memoElem[0].getText(); }; export const memoContentInput: LocatorObject = { - locator: "input[name='memo']", + locator: "input[name='memoContent']", method: 'css', }; export const editMemoButton: LocatorObject = { locator: '.editMemoButton', method: 'css' }; From f166ba696a9e5ed2ab0ec01e7b66e394823ab87c Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 21 Oct 2022 11:09:32 +0300 Subject: [PATCH 144/199] rearranged steps --- .../features/transactions.feature | 194 +++++++++--------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/packages/yoroi-extension/features/transactions.feature b/packages/yoroi-extension/features/transactions.feature index 61acdb2bcd..c2b6b3d667 100644 --- a/packages/yoroi-extension/features/transactions.feature +++ b/packages/yoroi-extension/features/transactions.feature @@ -4,54 +4,61 @@ Feature: Send transaction Given I have opened the extension And I have completed the basic setup - @it-54 - Scenario Outline: User can send funds from one Yoroi wallet to another (IT-54) + @invalidWitnessTest @it-20 + Scenario: Sending a Tx and receiving from the server an invalid signature error (IT-20) Given There is a Byron wallet stored named many-tx-wallet And I have a wallet with funds When I go to the send transaction screen And I fill the form: | address | amount | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | | - And The transaction fees are "" + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 | + And The transaction fees are "0.191505" And I click on the next button in the wallet send form And I see send money confirmation dialog - Then I should see no warning block And I enter the wallet password: | password | | asdfasdfasdf | - Given The expected transaction is And I submit the wallet send form - Then I should see the successfully sent page - And I click the transaction page button - Then I should see the summary screen - - - Examples: - | amount | fee | expectedTx | - | 1.000000 | 0.191505 | "g6QAgoJYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAQGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoAD0JAAhoACcQAAxoR/pTNoQKChFggjwZH2yB45FmtwUIDn2Z0sdrjUV0nh3/9w6+rIqAofKpYQJnz4URZfjl5AOpaEjMTWdF1lsFXPE+IKC0puBMdBjkWaqk6BIzW8VZ7pu7f8J/Mv8E3lRFiBVX7BnDQNYlT7g1YIEWHLG+QNh2fiHa9Yf1WMw3u39BreBNElUSItlalinAOQaCEWCDse/gGDkp4k1B1ag5M4bRRe29RzP/QQZMOvvpvEPv0s1hASaHptgaRcfgiyLBN80gqUIAueQBKkwSLu9iAd+UIN7PELThmN5ItmVzMeZIevE/7eS7wFX15sax9ap5ihK/1ClggVjNs6sBMbpP4HE6LrwaXJobTdjRCm+WpbheSv4cGYdhBoPY=" | - | 2.000000 | 0.199117 | "g6QAg4JYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI11AQGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoAHoSAAhoABwTgAxoR/pTNoQKDhFggjwZH2yB45FmtwUIDn2Z0sdrjUV0nh3/9w6+rIqAofKpYQLM12LTh1NoxSUm0bbVTQfygHhTVh6bLYXlVC4cnlUPGu45AojcnGEqZTE/+vItV7EvsBBlKTapopZofryrXrA5YIEWHLG+QNh2fiHa9Yf1WMw3u39BreBNElUSItlalinAOQaCEWCDse/gGDkp4k1B1ag5M4bRRe29RzP/QQZMOvvpvEPv0s1hAwPcLKhKTeclUAgVMRh7EHv2F+jv6zQ7fpsQwbSucHpESYz+8QamNsB45zU8ocp5K4WDesTR7nxE7I9lvBc0jAlggVjNs6sBMbpP4HE6LrwaXJobTdjRCm+WpbheSv4cGYdhBoIRYIMx77WUfp7RKzGSCIG6trkNgf4x7dE0yGdo9SczB9h07WEBwOJ1TbOoaNh9Uf+erO5luL5/5sIvddiala/3VB2vFJXbohZnvvrJvQoi5Cc0AthRftjupDgna8dmSt25s7WYFWCBYgDZGS5hmdznviyN0PIgAm/XR3K5WTeC/7Go04Pu/v0Gg9g==" | + Then I should see an invalid signature error message - @it-90 - Scenario Outline: Spending Password should be case-sensitive [Transaction confirmation] (IT-90) + @it-42 + Scenario: User can't send funds with incorrect Spending password (IT-42) Given There is a Byron wallet stored named many-tx-wallet And I have a wallet with funds When I go to the send transaction screen And I fill the form: | address | amount | | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 | + And The transaction fees are "0.191505" And I click on the next button in the wallet send form And I see send money confirmation dialog And I enter the wallet password: - | password | - | | + | password | + | WrongPassword | And I submit the wallet send form Then I should see an incorrect wallet password error message - Examples: - | password | - | secret_123 | - | SECRET_123 | - | sECRET_123 | + @it-46 + Scenario: User can't send funds to the invalid address (IT-46) + Given There is a Byron wallet stored named many-tx-wallet + And I have a wallet with funds + When I go to the send transaction screen + And I fill the form: + | address | amount | | + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMC | 0.001000 | Some characters in address has been changed and removed| + Then I should see an invalid address error + And I should not be able to submit + + @it-47 + Scenario: User can't send more funds than he has (IT-47) + Given There is a Byron wallet stored named many-tx-wallet + And I have a wallet with funds + When I go to the send transaction screen + And I fill the form: + | address | amount | + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 9007199255 | + Then I should see a not enough ada error + And I should not be able to submit @it-48 Scenario Outline: CONFIRM TRANSACTION Pop up displays properly (IT-48) @@ -67,31 +74,53 @@ Feature: Send transaction | address | amount |fee | |
    | | | - Examples: + Examples: | address | amount |fee | | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 |0.191505 | - @it-46 - Scenario: User can't send funds to the invalid address (IT-46) + @it-53 + Scenario: Sending a Tx changing a valid address for an invalid one (IT-53) Given There is a Byron wallet stored named many-tx-wallet And I have a wallet with funds When I go to the send transaction screen And I fill the form: - | address | amount | | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMC | 0.001000 | Some characters in address has been changed and removed| - Then I should see an invalid address error - And I should not be able to submit + | address | amount | + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 0.001000 | + And I clear the receiver + And I fill the receiver as "Invalid address" + Then I should not be able to submit + When I clear the receiver + And I fill the form: + | address | amount | + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdXXXX | 0.001000 | + Then I should not be able to submit - @it-47 - Scenario: User can't send more funds than he has (IT-47) + @it-54 + Scenario Outline: User can send funds from one Yoroi wallet to another (IT-54) Given There is a Byron wallet stored named many-tx-wallet And I have a wallet with funds When I go to the send transaction screen And I fill the form: - | address | amount | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 9007199255 | - Then I should see a not enough ada error - And I should not be able to submit + | address | amount | + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | | + And The transaction fees are "" + And I click on the next button in the wallet send form + And I see send money confirmation dialog + Then I should see no warning block + And I enter the wallet password: + | password | + | asdfasdfasdf | + Given The expected transaction is + And I submit the wallet send form + Then I should see the successfully sent page + And I click the transaction page button + Then I should see the summary screen + + + Examples: + | amount | fee | expectedTx | + | 1.000000 | 0.191505 | "g6QAgoJYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAQGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoAD0JAAhoACcQAAxoR/pTNoQKChFggjwZH2yB45FmtwUIDn2Z0sdrjUV0nh3/9w6+rIqAofKpYQJnz4URZfjl5AOpaEjMTWdF1lsFXPE+IKC0puBMdBjkWaqk6BIzW8VZ7pu7f8J/Mv8E3lRFiBVX7BnDQNYlT7g1YIEWHLG+QNh2fiHa9Yf1WMw3u39BreBNElUSItlalinAOQaCEWCDse/gGDkp4k1B1ag5M4bRRe29RzP/QQZMOvvpvEPv0s1hASaHptgaRcfgiyLBN80gqUIAueQBKkwSLu9iAd+UIN7PELThmN5ItmVzMeZIevE/7eS7wFX15sax9ap5ihK/1ClggVjNs6sBMbpP4HE6LrwaXJobTdjRCm+WpbheSv4cGYdhBoPY=" | + | 2.000000 | 0.199117 | "g6QAg4JYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI11AQGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoAHoSAAhoABwTgAxoR/pTNoQKDhFggjwZH2yB45FmtwUIDn2Z0sdrjUV0nh3/9w6+rIqAofKpYQLM12LTh1NoxSUm0bbVTQfygHhTVh6bLYXlVC4cnlUPGu45AojcnGEqZTE/+vItV7EvsBBlKTapopZofryrXrA5YIEWHLG+QNh2fiHa9Yf1WMw3u39BreBNElUSItlalinAOQaCEWCDse/gGDkp4k1B1ag5M4bRRe29RzP/QQZMOvvpvEPv0s1hAwPcLKhKTeclUAgVMRh7EHv2F+jv6zQ7fpsQwbSucHpESYz+8QamNsB45zU8ocp5K4WDesTR7nxE7I9lvBc0jAlggVjNs6sBMbpP4HE6LrwaXJobTdjRCm+WpbheSv4cGYdhBoIRYIMx77WUfp7RKzGSCIG6trkNgf4x7dE0yGdo9SczB9h07WEBwOJ1TbOoaNh9Uf+erO5luL5/5sIvddiala/3VB2vFJXbohZnvvrJvQoi5Cc0AthRftjupDgna8dmSt25s7WYFWCBYgDZGS5hmdznviyN0PIgAm/XR3K5WTeC/7Go04Pu/v0Gg9g==" | @it-55 Scenario Outline: User can send all funds from one Yoroi wallet to another (IT-55) @@ -118,66 +147,6 @@ Feature: Send transaction | fee | expectedTx | | 0.209237 | "g6QAiYJYILcTzA1jEGw4BrGnB3zDeilPzKDkefJqrGTlHgmugI11AIJYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAIJYIGBJO/JuYLC5jxQ2R2E74uwcb1C9X8FaFKL/UY9fo2vgAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAIJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI1xAYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI11AIJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgmugI11AYJYIAoHNmmEX+pK6DzUQYoLT9VmEAl6iWAagWtYkfZn40lsAIJYIAoHNmmEX+pK6DzUQYoLT9VmEAl6iWAagWtYkfZn40lsAQGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoALENMAhoAAzEpAxoR/pTNoQKGhFggXnGsSYUyhwTHwB1G9f7mkd9iHoKwNvA477DkhDTXmX5YQOgqSLhR8sUADEtWnvr6NjvmTSgZecfPXL4Rbu7TIp2UmflUqzzfx1vZ3J9wyB/h6smfq8mlqlqNVyNfO6j07A5YIO23hdKX7dxt3V0Cchr8MLvy1UwjID2hIegQ0F2VF6vHQaCEWCCPBkfbIHjkWa3BQgOfZnSx2uNRXSeHf/3Dr6sioCh8qlhAoi4s6bvzxVwn4Gkdsyewl2mPghpInwoRfQnmQIbGPcI1JoQpB64cB1cd9+f/w+uAt/t2aSU8DSFgkRkWddj9A1ggRYcsb5A2HZ+Idr1h/VYzDe7f0Gt4E0SVRIi2VqWKcA5BoIRYIOx7+AYOSniTUHVqDkzhtFF7b1HM/9BBkw6++m8Q+/SzWEDSvpZpGjk74C0E4gieWGRuFWhdnP4640l259qPRUzr5TnkUhc5BRq/ubxVC6LBqDd11PZa/g8XWGoFUJiY+toAWCBWM2zqwExuk/gcTouvBpcmhtN2NEKb5aluF5K/hwZh2EGghFggzHvtZR+ntErMZIIgbq2uQ2B/jHt0TTIZ2j1JzMH2HTtYQOCCXW2Lo0iUC+HsPqg/MgSbR3Rb+Z/WLxDhQ4t9qeZYnoXUDPOZWMOz9ToWLibo+75g5IzTK+CNWtrgM5BtmAdYIFiANkZLmGZ3Oe+LI3Q8iACb9dHcrlZN4L/sajTg+7+/QaCEWCCrz2wieKqlEL0JsQicx3naLrAb0qgH4COJ/AE3AZvCd1hATLVkXVaSCrYGXA3jOCO9JSZiqhyRYLG1IZaSVETBS9Xb9ovg+kCCX8V43NxNuwLVQ5udSiJKv5Sbf9CUhpfyAVggXsJ5mvWuSD0y///s18T45fzDjpah0vqXB3erntI0INFBoIRYILlNNm5mIlb6YPjq4n8gjDChi+2y90adZ81HgfMcpj8GWEDjpBRrBC2WEwVLLgI0qgXRYmxNAtjRcPdEaFKTugqQ0/vup0jP1r4wMq6rqkqMWOWdaodtClZs3KBIW1cXWv0FWCCux11USi/M3OmzDhjc8euPOY/1uQF34sJFYQk85C/rpUGg9g==" | - @invalidWitnessTest @it-20 - Scenario: Sending a Tx and receiving from the server an invalid signature error (IT-20) - Given There is a Byron wallet stored named many-tx-wallet - And I have a wallet with funds - When I go to the send transaction screen - And I fill the form: - | address | amount | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 | - And The transaction fees are "0.191505" - And I click on the next button in the wallet send form - And I see send money confirmation dialog - And I enter the wallet password: - | password | - | asdfasdfasdf | - And I submit the wallet send form - Then I should see an invalid signature error message - - @it-42 - Scenario: User can't send funds with incorrect Spending password (IT-42) - Given There is a Byron wallet stored named many-tx-wallet - And I have a wallet with funds - When I go to the send transaction screen - And I fill the form: - | address | amount | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 | - And The transaction fees are "0.191505" - And I click on the next button in the wallet send form - And I see send money confirmation dialog - And I enter the wallet password: - | password | - | WrongPassword | - And I submit the wallet send form - Then I should see an incorrect wallet password error message - - @it-53 - Scenario: Sending a Tx changing a valid address for an invalid one (IT-53) - Given There is a Byron wallet stored named many-tx-wallet - And I have a wallet with funds - When I go to the send transaction screen - And I fill the form: - | address | amount | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 0.001000 | - And I clear the receiver - And I fill the receiver as "Invalid address" - Then I should not be able to submit - When I clear the receiver - And I fill the form: - | address | amount | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdXXXX | 0.001000 | - Then I should not be able to submit - - @it-89 - Scenario: Try to make a transactions from the empty wallet (IT-89) - Given There is a Byron wallet stored named empty-wallet - When I go to the send transaction screen - And I fill the form: - | address | amount | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 | - Then I should see a not enough ada error - @it-59 Scenario: Display warning if wallet changes during confirmation (IT-59) Given There is a Byron wallet stored named many-tx-wallet @@ -243,6 +212,37 @@ Feature: Send transaction Then A pending tx gets sent from my wallet from another client Then I should see a warning block + @it-89 + Scenario: Try to make a transactions from the empty wallet (IT-89) + Given There is a Byron wallet stored named empty-wallet + When I go to the send transaction screen + And I fill the form: + | address | amount | + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 | + Then I should see a not enough ada error + + @it-90 + Scenario Outline: Spending Password should be case-sensitive [Transaction confirmation] (IT-90) + Given There is a Byron wallet stored named many-tx-wallet + And I have a wallet with funds + When I go to the send transaction screen + And I fill the form: + | address | amount | + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 | + And I click on the next button in the wallet send form + And I see send money confirmation dialog + And I enter the wallet password: + | password | + | | + And I submit the wallet send form + Then I should see an incorrect wallet password error message + + Examples: + | password | + | secret_123 | + | SECRET_123 | + | sECRET_123 | + @it-137 Scenario: Test Shelley wallet delegation (IT-137) Given There is a Shelley wallet stored named shelley-simple-15 From 6c058c0eac678ed19f621712694677bf4d41e477 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 21 Oct 2022 13:26:47 +0300 Subject: [PATCH 145/199] fixed ui settings --- .../features/settings-ui.feature | 218 +++++++++--------- .../step_definitions/settings-ui-steps.js | 4 +- 2 files changed, 110 insertions(+), 112 deletions(-) diff --git a/packages/yoroi-extension/features/settings-ui.feature b/packages/yoroi-extension/features/settings-ui.feature index a73c12bce6..72d9bedd46 100644 --- a/packages/yoroi-extension/features/settings-ui.feature +++ b/packages/yoroi-extension/features/settings-ui.feature @@ -4,6 +4,48 @@ Feature: Wallet UI Settings Given I have opened the extension And I have completed the basic setup + @it-2 + Scenario: Change language in General Settings (IT-2) + When I navigate to the general settings screen + And I open General Settings language selection dropdown + And I select Japanese language + Then The Japanese language should be selected + When I refresh the page + Then The Japanese language should be selected + + @it-3 + Scenario: Yoroi Settings Screen / Terms of Use in Default English(IT-3) + And I navigate to the general settings screen + And I click on secondary menu "Terms of use" item + Then I should see the "Terms of use" screen + + @it-4 + Scenario: Yoroi Settings Screen / Support (IT-4) + And I navigate to the general settings screen + And I click on secondary menu "support" item + Then I should see support screen + + @it-8 + Scenario Outline: Wallet renaming (IT-8) + And There is a Byron wallet stored named empty-wallet + And I navigate to the general settings screen + And I click on secondary menu "wallet" item + And I click on "name" input field + And I enter new wallet name: + | name | + | | + And I click outside "name" input field + And I navigate to wallet sidebar category + Then I should see new wallet name "" + Examples: + | walletName | | + | first Edited | 2 words name | + | ウォレットの追加 | Japanese | + | 지갑 추가 | Korean | + | НАСТРОЙКИ | Russian | + | a | 1-characters length | + | asdfghjklpoiuytrewqazxcvbnmlkjhgfdsaqwer | 40 characters length | + @it-12 Scenario Outline: User can't change password if it doesn't meet complexity requirements (IT-12) And There is a Byron wallet stored named empty-wallet @@ -23,81 +65,43 @@ Feature: Wallet UI Settings | currentPassword | password | repeatedPassword | | asdfasdfasdf | Secre1 | Secre1 | - @it-94 - Scenario Outline: User is able to change spending password (IT-94) - And There is a Byron wallet stored named tx-big-input-wallet - And I have a wallet with funds + @it-14 + Scenario: User can't change the password without entering old password (IT-14) + And There is a Byron wallet stored named empty-wallet And I navigate to the general settings screen And I click on secondary menu "wallet" item And I click on the "change" password label And I should see the "change" wallet password dialog And I change wallet password: - | currentPassword | password | repeatedPassword | - | asdfasdfasdf | newSecret123 | newSecret123 | + | currentPassword | password | repeatedPassword | + | asdfasdfasdf | newSecret123 | newSecret123 | + And I clear the current wallet password asdfasdfasdf And I submit the wallet password dialog - Then I should not see the change password dialog anymore + Then I should see "Incorrect wallet password." error message - When I navigate to wallet sidebar category - And I go to the send transaction screen - And I fill the form: - | address | amount | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | | - And The transaction fees are "" - And I click on the next button in the wallet send form - And I see send money confirmation dialog - And I enter the wallet password: - | password | - | newSecret123 | - And I submit the wallet send form - Then I should see the successfully sent page - And I click the transaction page button - Then I should see the summary screen + @it-23 + Scenario: Wallet settings tab isn't active if wallet is not created (IT-23) + When There is no wallet stored + And I navigate to the general settings screen + Then I click on secondary menu "wallet" item + Then I should see a no wallet message - Examples: - | amount | fee | - | 1.000000 | 0.168801 | - - @it-91 - Scenario Outline: Password should be case-sensitive [Wallet password changing] (IT-91) + @it-40 + Scenario: User can't change password without filling Password repeat field (IT-40) And There is a Byron wallet stored named empty-wallet And I navigate to the general settings screen And I click on secondary menu "wallet" item And I click on the "change" password label And I should see the "change" wallet password dialog And I change wallet password: - | currentPassword | password | repeatedPassword | - | | | | - And I submit the wallet password dialog - Then I should see the following submit error messages: - | message | - | | - Examples: - | currentPassword | password |repeatedPassword |errorMessage| - | aaSecreT_123 | ValidPassword123 |ValidPassword123 |api.errors.IncorrectPasswordError| - | aaseCReT_123 | ValidPassword123 |ValidPassword123 |api.errors.IncorrectPasswordError| - | aaSEcRET_123 | ValidPassword123 |ValidPassword123 |api.errors.IncorrectPasswordError| - - - @it-8 - Scenario Outline: Wallet renaming (IT-8) - And There is a Byron wallet stored named empty-wallet - And I navigate to the general settings screen - And I click on secondary menu "wallet" item - And I click on "name" input field - And I enter new wallet name: - | name | - | | - And I click outside "name" input field - And I navigate to wallet sidebar category - Then I should see new wallet name "" - Examples: - | walletName | | - | first Edited | 2 words name | - | ウォレットの追加 | Japanese | - | 지갑 추가 | Korean | - | НАСТРОЙКИ | Russian | - | a | 1-characters length | - | asdfghjklpoiuytrewqazxcvbnmlkjhgfdsaqwer | 40 characters length | + | currentPassword | password | repeatedPassword | + | aaSecret_123 | newSecret123 | newSecret123 | + And I clear the current wallet repeat password newSecret123 + Then I see the submit button is disabled + And I should stay in the change password dialog + And I should see "Doesn't match" error message: + | message | + | global.errors.invalidRepeatPassword | @it-41 Scenario Outline: Wallet can't be renamed if new wallet name doesn't meet requirements (IT-41) @@ -106,75 +110,69 @@ Feature: Wallet UI Settings And I click on secondary menu "wallet" item And I click on "name" input field And I enter new wallet name: - | name | - | | + | name | + | | And I click outside "name" input field Then I should see "Wallet name requires at least 1 and at most 40 letters." error message: - | message | - | global.errors.invalidWalletName | + | message | + | global.errors.invalidWalletName | Examples: - | walletName | | - | asdfghjklpoiuytrewqazxcvbnmlkjhgfdsaqwerd |41 characters length | + | walletName | | + | asdfghjklpoiuytrewqazxcvbnmlkjhgfdsaqwerd |41 characters length | - @it-14 - Scenario: User can't change the password without entering old password (IT-14) + @it-91 + Scenario Outline: Password should be case-sensitive [Wallet password changing] (IT-91) And There is a Byron wallet stored named empty-wallet And I navigate to the general settings screen And I click on secondary menu "wallet" item And I click on the "change" password label And I should see the "change" wallet password dialog And I change wallet password: - | currentPassword | password | repeatedPassword | - | asdfasdfasdf | newSecret123 | newSecret123 | - And I clear the current wallet password asdfasdfasdf + | currentPassword | password | repeatedPassword | + | | | | And I submit the wallet password dialog - Then I should see "Incorrect wallet password." error message + Then I should see the following submit error messages: + | message | + | | + Examples: + | currentPassword | password |repeatedPassword |errorMessage| + | aaSecreT_123 | ValidPassword123 |ValidPassword123 |api.errors.IncorrectPasswordError| + | aaseCReT_123 | ValidPassword123 |ValidPassword123 |api.errors.IncorrectPasswordError| + | aaSEcRET_123 | ValidPassword123 |ValidPassword123 |api.errors.IncorrectPasswordError| - @it-40 - Scenario: User can't change password without filling Password repeat field (IT-40) - And There is a Byron wallet stored named empty-wallet + @it-94 + Scenario Outline: User is able to change spending password (IT-94) + And There is a Byron wallet stored named tx-big-input-wallet + And I have a wallet with funds And I navigate to the general settings screen And I click on secondary menu "wallet" item And I click on the "change" password label And I should see the "change" wallet password dialog And I change wallet password: | currentPassword | password | repeatedPassword | - | aaSecret_123 | newSecret123 | newSecret123 | - And I clear the current wallet repeat password newSecret123 - Then I see the submit button is disabled - And I should stay in the change password dialog - And I should see "Doesn't match" error message: - | message | - | global.errors.invalidRepeatPassword | - - @it-2 - Scenario: Change language in General Settings (IT-2) - When I navigate to the general settings screen - And I open General Settings language selection dropdown - And I select Japanese language - Then The Japanese language should be selected - When I refresh the page - Then The Japanese language should be selected - - @it-3 - Scenario: Yoroi Settings Screen / Terms of Use in Default English(IT-3) - And I navigate to the general settings screen - And I click on secondary menu "Terms of use" item - Then I should see the "Terms of use" screen - - @it-23 - Scenario: Wallet settings tab isn't active if wallet is not created (IT-23) - When There is no wallet stored - And I navigate to the general settings screen - Then I click on secondary menu "wallet" item - Then I should see a no wallet message + | asdfasdfasdf | newSecret123 | newSecret123 | + And I submit the wallet password dialog + Then I should not see the change password dialog anymore + When I navigate to wallet sidebar category + And I go to the send transaction screen + And I fill the form: + | address | amount | + | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | | + And The transaction fees are "" + And I click on the next button in the wallet send form + And I see send money confirmation dialog + And I enter the wallet password: + | password | + | newSecret123 | + And I submit the wallet send form + Then I should see the successfully sent page + And I click the transaction page button + Then I should see the summary screen - @it-4 - Scenario: Yoroi Settings Screen / Support (IT-4) - And I navigate to the general settings screen - And I click on secondary menu "support" item - Then I should see support screen + Examples: + | amount | fee | + | 1.000000 | 0.180065 | @it-125 Scenario: Switch complexity levels (IT-125) diff --git a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js index eacd19015d..16b372785c 100644 --- a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js @@ -138,7 +138,6 @@ Then(/^I should see support screen$/, async function () { Then(/^I should see blockchain screen$/, async function () { await this.waitForElement(explorerSettingsDropdown); await this.waitForElement(cardanoPaymentsURLTitle); - await this.waitForElement(currencyConversionText); }); When(/^I click on remove wallet$/, async function () { @@ -174,5 +173,6 @@ Then(/^I sleep for ([^"]*)$/, async function (ms) { }); Then(/^I should see "Incorrect wallet password." error message$/, async function () { - await this.waitUntilText(changePasswordDialogError, 'Incorrect wallet password.', 15000); + const errorMessage = await i18n.formatMessage(this.driver, { id: 'api.errors.IncorrectPasswordError' }); + await this.waitUntilText(changePasswordDialogError, errorMessage); }); From 9213241dc70ebd126acdb24dcaa2181ab2f509ff Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 21 Oct 2022 13:27:16 +0300 Subject: [PATCH 146/199] fixed input memo locator --- packages/yoroi-extension/features/pages/walletSendPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js index 84495dd34c..13c0eab456 100644 --- a/packages/yoroi-extension/features/pages/walletSendPage.js +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -36,8 +36,8 @@ export const getMemoText = async (customWorld: Object): Promise => { return await memoElem[0].getText(); }; export const memoContentInput: LocatorObject = { - locator: "input[name='memoContent']", - method: 'css', + locator: '//input[contains(@id, "memo")]', + method: 'xpath', }; export const editMemoButton: LocatorObject = { locator: '.editMemoButton', method: 'css' }; export const deleteMemo = async (customWorld: Object, confirmDeleting: boolean = true) => { From a8cc3b39f976bee84d4325aaa1c4e32dcea2f264 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 21 Oct 2022 17:46:18 +0300 Subject: [PATCH 147/199] added the method scrollIntoView --- .../yoroi-extension/features/pages/walletReceivePage.js | 2 +- .../step_definitions/addresses-generation-steps.js | 8 +++++--- packages/yoroi-extension/features/support/webdriver.js | 5 +++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletReceivePage.js b/packages/yoroi-extension/features/pages/walletReceivePage.js index d859cf4ad1..034939d8cb 100644 --- a/packages/yoroi-extension/features/pages/walletReceivePage.js +++ b/packages/yoroi-extension/features/pages/walletReceivePage.js @@ -10,7 +10,7 @@ const getReceiveSubTabButton = (translatedText: string) => { export const getGeneratedAddress = (rowIndex: number): LocatorObject => { return { - locator: `.generatedAddress-${rowIndex + 1} .RawHash_hash`, + locator: `.generatedAddress-${rowIndex + 1} .WalletReceive_addressHash`, method: 'css', }; }; diff --git a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js index 01b8117bb7..22572aa019 100644 --- a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js @@ -100,9 +100,11 @@ Then(/^I see every generated address is unique$/, async function () { Then(/^I should see the addresses exactly list them$/, async function (table) { const rows = table.hashes(); - const waitUntilAddressesAppeared = rows.map((row, index) => - this.waitUntilText(getGeneratedAddress(index), truncateAddressShort(row.address)) - ); + const waitUntilAddressesAppeared = rows.map(async (row, index) => { + const addressLocator = getGeneratedAddress(index); + await this.scrollIntoView(addressLocator); + return this.waitUntilText(addressLocator, truncateAddressShort(row.address)) + }); const noMoreAddressAppeared = this.waitForElementNotPresent( getGeneratedAddress(rows.length + 1) ); diff --git a/packages/yoroi-extension/features/support/webdriver.js b/packages/yoroi-extension/features/support/webdriver.js index 628eb221e3..fb0177cb26 100644 --- a/packages/yoroi-extension/features/support/webdriver.js +++ b/packages/yoroi-extension/features/support/webdriver.js @@ -404,6 +404,11 @@ function CustomWorld(cmdInput: WorldInput) { const actions = this.driver.actions(); await actions.move({ origin: locator }).perform(); }; + + this.scrollIntoView = async (locator: LocatorObject) => { + const element = await this.getElementBy(locator); + await this.driver.executeScript('arguments[0].scrollIntoView();', element); + }; } // no need to await From 63ffd419d7eb72eeed900f3ee986c3e70a63be3d Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 21 Oct 2022 17:55:21 +0300 Subject: [PATCH 148/199] using scrollIntoView to reach all addresses --- .../features/step_definitions/addresses-generation-steps.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js index 22572aa019..e1e4ccd2c5 100644 --- a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js @@ -62,8 +62,11 @@ When(/^I click on the HasBalance addresses button$/, async function () { }); When(/^I click on the Generate new address button ([0-9]+) times$/, async function (times) { - for (let curr = 1; curr <= times; curr++) { + for (let curr = 0; curr < times; curr++) { + await this.scrollIntoView(generateAddressButton); await this.click(generateAddressButton); + const addrLocator = getGeneratedAddress(curr); + await this.scrollIntoView(addrLocator); await this.waitForElement(getGeneratedAddress(curr)); } }); From fd3b64995439d52543089fb5e12ae643b634c417 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 21 Oct 2022 20:14:49 +0300 Subject: [PATCH 149/199] added "sleep" after opening the Receive tab, we need some time before address will be displayed --- .../features/step_definitions/addresses-generation-steps.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js index e1e4ccd2c5..919d27c5f3 100644 --- a/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js +++ b/packages/yoroi-extension/features/step_definitions/addresses-generation-steps.js @@ -22,13 +22,16 @@ import { walletAddressRow, getAddressFromAddressRow, } from '../pages/walletReceivePage'; +import { halfSecond, oneSecond } from '../support/helpers/common-constants'; Given(/^Revamp. I go to the receive screen$/, async function () { await this.click(receiveTab); + await this.driver.sleep(oneSecond + halfSecond); }); Given(/^I go to the receive screen$/, async function () { await this.click(receiveTab); + await this.driver.sleep(oneSecond + halfSecond); }); When(/^I click on the Generate new address button$/, async function () { From 4fe9bf0212753a7262334a8f8737777520008d26 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 21 Oct 2022 20:34:51 +0300 Subject: [PATCH 150/199] Removed tests for ledger devices --- .../yoroi-extension/features/hardware.feature | 132 ------------------ .../features/mock-ledger-connect/index.js | 4 + .../features/step_definitions/common-steps.js | 1 + .../step_definitions/hardware-steps.js | 5 +- 4 files changed, 9 insertions(+), 133 deletions(-) delete mode 100644 packages/yoroi-extension/features/hardware.feature diff --git a/packages/yoroi-extension/features/hardware.feature b/packages/yoroi-extension/features/hardware.feature deleted file mode 100644 index 41b9caa73f..0000000000 --- a/packages/yoroi-extension/features/hardware.feature +++ /dev/null @@ -1,132 +0,0 @@ -Feature: Hardware device - - Background: - Given I have opened the extension - And I have completed the basic setup - - @it-119 - Scenario: Test Byron Ledger (IT-119) - Given I connected Ledger device 707fa118bf6b83 - # test restoration - When I select a Byron-era Ledger device - And I restore the Ledger device - Then I should see the summary screen - Then I should see a plate JSKA-2258 - # test sending - Given I go to the send transaction screen - And I fill the form: - | address | amount | - | Ae2tdPwUPEZ3HUU7bmfexrUzoZpAZxuyt4b4bn7fus7RHfXoXRightdgMCv | 1.000000 | - Then I add a transaction memo that says "my awesome memo" - And I click on the next button in the wallet send form - Then I see the hardware send money confirmation dialog - Given The expected transaction is "hKQAgYJYIBZt/eWxg7fglIOvu/zntB59b+00tAXMEEG0XyfosF1HAAGBglgrgtgYWCGDWBxA66wjgpZH9bPPW9JExWBcIUqKGMmxBlNBcCzWoAAa8oZfOxoAD0JAAhoACb4hAxoR/pTNoQKBhFgg8elBZl4BQGla+Tli4OXssZO+OKu0a4uE90ZUlPqOjApYQFfNdN84xjlYUK+VJmNfrCv5BsNoKskjkCN4ixLwUUrICfdRrmvqgAdQI2eczTSA1wvWZjl8mkFwPxNtZVjH6gVYINNT5ssGb8HUyAOT9k5hdlJG+NKzPSnWME87YabidYnjQaD19g==" - Then I submit the wallet send form - Then I should see the successfully sent page - And I click the transaction page button - Then I should see the summary screen - And I expand the top transaction - Then The memo content says "my awesome memo" - # test address verification - When I go to the receive screen - Given I should see the Receive screen - And I click on the verify address button - Then I see the verification address "Ae2tdPwUPEYxqRJXnstgBN88qtjtDVNRXD5Ghm3wK9NS7fhKRseQ2TVVpth" - And I see the derivation path "m/44'/1815'/0'/0/1" - Then I verify the address on my ledger device - - @it-116 - Scenario: Test Shelley Ledger (IT-116) - Given I connected Ledger device 707fa118bf6b83 - # test restoration - When I select a Shelley-era Ledger device - And I restore the Ledger device - And I click skip the transfer - Then I should see the dashboard screen - Then I should see a plate KHDC-5476 - # test sending - Given I go to the send transaction screen - And I fill the form: - | address | amount | - | Ae2tdPwUPEZAVDjkPPpwDhXMSAjH53CDmd2xMwuR9tZMAZWxLhFphrHKHXe | 1.000000 | - Then I add a transaction memo that says "my awesome memo" - And I click on the next button in the wallet send form - Then I see the hardware send money confirmation dialog - Given The expected transaction is "hKQAgYJYIDZ351x7ppmv3GzVfULyRvhvaa79dgJQBqx4MT+tK7ohAQGCglgrgtgYWCGDWByJGsmrqsmZsJfIHqPARQsPu2k9C9IyvrwPSjkfoAAa8v9+IRoAD0JAglg5AXU/qJHPfuF4AaA3RBmWAEAnGtmf6+kXwe182LUPZi1s6xtlczppoe1y+G8LrFoWUFoCiJevG+NFGgBCGwsCGgACjxUDGhH+lM2hAIGCWCA2j8onDd+7a+1yUcwDde1mFT4vweBhzNgescbtdEs7BVhAptWz/dmziL1SKTguqmcbzD8o1xiaGUMAijhl2oCy7AfHwFZSewXWi+eQQEJX/umPAjjcnjZjkDEc65kECAUCBPX2" - Then I submit the wallet send form - Then I should see the successfully sent page - And I click the transaction page button - Then I should see the summary screen - # test address verification - When I go to the receive screen - Given I should see the Receive screen - And I click on the verify address button - Then I see the verification address "addr1qx7ef2pmnrl3ejempwfnn920ukm2rftj7untkcgsvulrgzc0vckke6cmv4en56dpa4e0smct43dpv5z6q2yf0tcmudzst5ydks" - And I see the derivation path "m/1852'/1815'/0'/0/1" - Then I verify the address on my ledger device - - @it-138 - Scenario: Test Shelley Ledger upgrade transaction (IT-138) - Given I connected Ledger device 707fa118bf6b83 - # test restoration - When I select a Shelley-era Ledger device - And I restore the Ledger device - Given The expected transaction is "hKQAgYJYIBZt/eWxg7fglIOvu/zntB59b+00tAXMEEG0XyfosF1HAAGBglg5AXU/qJHPfuF4AaA3RBmWAEAnGtmf6+kXwe182LUPZi1s6xtlczppoe1y+G8LrFoWUFoCiJevG+NFGgAWc+ACGgACjIEDGhH+lM2hAoGEWCDx6UFmXgFAaVr5OWLg5eyxk744q7Rri4T3RlSU+o6MClhA8JNsFwN1WFUz6F3jt72Xgdwc0k9NwIs7X1P1vpumle3BmRxf2eTL4uSZJipKy2MLiJjP7eZWeWgN2QVcjPmnBFgg01PmywZvwdTIA5P2TmF2Ukb40rM9KdYwTzthpuJ1ieNBoPX2" - Then I see the transfer transaction - And I accept the prompt - Then I should see the dashboard screen - Then I go to the tx history screen - And I should see that the number of transactions is 2 - And I should see 1 pending transactions - - @it-100 - Scenario: Test Shelley Ledger delegation (IT-100) - Given I connected Ledger device 707fa118bf6b83 - When I select a Shelley-era Ledger device - And I restore the Ledger device - And I click skip the transfer - Then I should see the dashboard screen - Then I should see a plate KHDC-5476 - # test delegation - Given I go to the delegation by id screen - And I fill the delegation id form: - | stakePoolId | - | df1750df9b2df285fcfb50f4740657a18ee3af42727d410c37b86207 | - Then I see the stakepool ticker "YOROI" - And I click on the next button in the delegation by id - Then I see the delegation confirmation dialog - Given The expected transaction is "hKUAgYJYIDZ351x7ppmv3GzVfULyRvhvaa79dgJQBqx4MT+tK7ohAQGBglg5AXU/qJHPfuF4AaA3RBmWAEAnGtmf6+kXwe182LUPZi1s6xtlczppoe1y+G8LrFoWUFoCiJevG+NFGgAyvwMCGgACqN0DGhH+lM0EgoIAggBYHA9mLWzrG2VzOmmh7XL4bwusWhZQWgKIl68b40WDAoIAWBwPZi1s6xtlczppoe1y+G8LrFoWUFoCiJevG+NFWBzfF1Dfmy3yhfz7UPR0BlehjuOvQnJ9QQw3uGIHoQCCglggNo/KJw3fu2vtclHMA3XtZhU+L8HgYczYHrHG7XRLOwVYQE0YC2UzEBrWg4P4ayvC4rwO83E8ZXTGa/KYuxehREgflqxgVKZ7xUwLNkrpR3nKmvIQDKVbpA2oah/weopxzwWCWCCJBYq1BjLaHESdxLaCRYL2F8gcQ7Zqu0RfZ1/u85XwPlhA0jtEUXVlkf0GbPRTWZGWpyM9sXh1+ht9xZIIbwqKvKtF5eZtwMtDE7ArbJzjhrpNvHWaxqIk8r1+YmNGqZtlAvX2" - Then I submit the wallet send form - Given I click on see dashboard - Then I should see the dashboard screen - - @it-120 @ignore - Scenario: Test Byron Trezor (IT-120) - Given I connected Trezor device 6495958994A4025BB5EE1DB0 - # test restoration - When I select a Byron-era Trezor device - And I restore the Trezor device - Then I should see the summary screen - Then I should see a plate CZSA-2051 - # test sending - Given I go to the send transaction screen - And I fill the form: - | address | amount | - | Ae2tdPwUPEZAVDjkPPpwDhXMSAjH53CDmd2xMwuR9tZMAZWxLhFphrHKHXe | 5.144385 | - Then I add a transaction memo that says "my awesome memo" - And I click on the next button in the wallet send form - Then I see the hardware send money confirmation dialog - Given The expected transaction is "g6QAg4JYIDZ351x7ppm/3GzVfULyRvhvaa79dgJQBqx4MT+tK7ogAYJYIAWEBYkvZgddg6vRt/40HS1b/S9hIrL4dHAAOeUHjg3VAYJYIBAp7vW7DwaXmrC5UwpiusEeGAeX0IyrmA/jk4nUKzZXAAGBglgrgtgYWCGDWByJGsmrqsmZsJfIHqPARQsPu2k9C9IyvrwPSjkfoAAa8v9+IRoATn9BAhoAAsX1AxoR/pTNoQKDhFgg/OzJFHxPlMKFDW9EFxmYPVVgPVzugQEeJKi8G6Z53SBYQL6AGt9yNRSaA62/trcYpxUTx3b3g0zn+sRN1dRj2B9WH6MRJE6fDab5MqSab9TpGNNG4xPcpSoE1aepTVBmfAVYIDV5JxLCyqvpBdt0le+EfezS1nIHZ/SVPbtaFugdNbENQaCEWCBuJwykTKqtfi5OyMYeJG+UyCLYack5wLGIlNEp24UZVlhAP7nKpcJyuREGWncNtFNy1ZOqifzgi0Cji4oBRuTv5hapZWSEL1fRVitJVWL7FSdHEXaPsa1OOfu+ZzXWhQKaAFggfWWCXtxX4sbaJDEuCcMwUmas37GGCd6TftqcwcstFCZBoIRYIO9odrsMMrrk705nbVG5FWZXouCzkB8U8VHjAMiKu+FbWECGGzqdT95Z8q+gE1e7Ax897Unj62yPC80ru7EPo5fJenRmoOwv+kWefItSZArK9YAv5Vs2qMsfIL9HjlcwGr0LWCDbr4V5V0+JN2EAd8rerGsUQVAxeKvkdq4sVrOvsCZ9akGg9g==" - Then I submit the wallet send form - Then I should see the successfully sent page - And I click the transaction page button - Then I should see the summary screen - And I expand the top transaction - Then The memo content says "my awesome memo" - # test address verification - When I go to the receive screen - Given I should see the Receive screen - And I click on the verify address button - Then I see the verification address "Ae2tdPwUPEZAVDjkPPpwDhXMSAjH53CDmd2xMwuR9tZMAZWxLhFphrHKHXe" - And I see the derivation path "m/44'/1815'/0'/0/8" - Then I verify the address on my trezor device \ No newline at end of file diff --git a/packages/yoroi-extension/features/mock-ledger-connect/index.js b/packages/yoroi-extension/features/mock-ledger-connect/index.js index 5fee402254..37dc634c52 100644 --- a/packages/yoroi-extension/features/mock-ledger-connect/index.js +++ b/packages/yoroi-extension/features/mock-ledger-connect/index.js @@ -33,6 +33,10 @@ import { StakeCredentialParamsType, } from '@cardano-foundation/ledgerjs-hw-app-cardano'; +/* +Mocking a ledger device is not possible anymore after this update https://github.com/Emurgo/yoroi-frontend/pull/2896 + */ + type WalletInfo = {| rootKey: RustModule.WalletV4.Bip32PrivateKey; serial: GetSerialResponse, diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 8a3d00a269..ed9871a03f 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -575,6 +575,7 @@ async function setLedgerWallet(client, serial) { .catch(err => done(err)); }, serial); } +// deprecated Given(/^I connected Ledger device ([^"]*)$/, async function (serial) { this.webDriverLogger.info(`Step: I connected Ledger device ${serial}`); await setLedgerWallet(this, serial); diff --git a/packages/yoroi-extension/features/step_definitions/hardware-steps.js b/packages/yoroi-extension/features/step_definitions/hardware-steps.js index cdae21e452..57da1cd5fb 100644 --- a/packages/yoroi-extension/features/step_definitions/hardware-steps.js +++ b/packages/yoroi-extension/features/step_definitions/hardware-steps.js @@ -22,6 +22,7 @@ import { errorBlockComponent, primaryButton } from '../pages/commonDialogPage'; import { walletNameInput } from '../pages/restoreWalletPage'; import { verifyAddressButton, verifyAddressHWButton } from '../pages/walletReceivePage'; +// deprecated When(/^I select a Byron-era Ledger device$/, async function () { await this.click(connectHwButton); await this.waitForElement(pickUpCurrencyDialog); @@ -30,6 +31,7 @@ When(/^I select a Byron-era Ledger device$/, async function () { await this.click(ledgerWalletButton); await this.click(byronEraButton); }); +// deprecated When(/^I select a Shelley-era Ledger device$/, async function () { await this.click(connectHwButton); await this.waitForElement(pickUpCurrencyDialog); @@ -38,6 +40,7 @@ When(/^I select a Shelley-era Ledger device$/, async function () { await this.click(ledgerWalletButton); await this.click(shelleyEraButton); }); +// deprecated When(/^I restore the Ledger device$/, async function () { await this.waitForElement(checkDialog); await this.click(primaryButton); @@ -95,7 +98,7 @@ When(/^I see the verification address "([^"]*)"$/, async function (expectAddress When(/^I see the derivation path "([^"]*)"$/, async function (path) { await this.waitUntilText(derivationField, path); }); - +// deprecated Then(/^I verify the address on my ledger device$/, async function () { await this.click(verifyAddressHWButton); await this.waitDisable(verifyAddressHWButton); // disable when communicating with device From 133a08b36d25ccef60a8b60c7955b3564f63d00a Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 24 Oct 2022 13:11:34 +0300 Subject: [PATCH 151/199] Fixed locators --- .../pages/walletTransactionsHistoryPage.js | 8 ++++++ .../step_definitions/tx-history-steps.js | 28 ++++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js b/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js index f386f0a190..fc3f710380 100644 --- a/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js +++ b/packages/yoroi-extension/features/pages/walletTransactionsHistoryPage.js @@ -81,3 +81,11 @@ export const transactionAddressListElement: LocatorObject = { locator: '.Transaction_addressList', method: 'css', }; +export const confirmationCountText: LocatorObject = { + locator: '.confirmationCount', + method: 'css', +}; +export const transactionIdText: LocatorObject = { + locator: '.txid', + method: 'css', +}; diff --git a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js index 7dcf7e5c11..cf95ca9b93 100644 --- a/packages/yoroi-extension/features/step_definitions/tx-history-steps.js +++ b/packages/yoroi-extension/features/step_definitions/tx-history-steps.js @@ -6,6 +6,7 @@ import { expect, AssertionError } from 'chai'; import moment from 'moment'; import i18n from '../support/helpers/i18n-helpers'; import { + confirmationCountText, failedTransactionElement, getTopTx, getTxStatus, @@ -15,10 +16,11 @@ import { pendingTransactionElement, showMoreButton, transactionAddressListElement, - transactionComponent, + transactionComponent, transactionIdText, } from '../pages/walletTransactionsHistoryPage'; import { summaryTab } from '../pages/walletPage'; import { displayInfo , txSuccessfulStatuses } from '../support/helpers/common-constants'; +import { getMethod } from '../support/helpers/helpers'; function verifyAllTxsFields( txType, @@ -128,8 +130,12 @@ Then(/^I verify top transaction content ([^"]*)$/, async function (walletName) { const topTx = actualTxsList[0]; let status = 'successful'; - const pending = await topTx.findElements(By.css('.Transaction_pendingLabel')); - const failed = await topTx.findElements(By.css('.Transaction_failedLabel')); + const pending = await topTx.findElements( + getMethod(pendingTransactionElement.method)(pendingTransactionElement.locator) + ); + const failed = await topTx.findElements( + getMethod(failedTransactionElement.method)(failedTransactionElement.locator) + ); if (pending.length > 0) { status = 'pending'; } else if (failed.length > 0) { @@ -138,7 +144,9 @@ Then(/^I verify top transaction content ([^"]*)$/, async function (walletName) { await topTx.click(); - const txList = await topTx.findElements(transactionAddressListElement); + const txList = await topTx.findElements( + getMethod(transactionAddressListElement.method)(transactionAddressListElement.locator) + ); const fromTxInfo = await parseTxInfo(txList[0]); const toTxInfo = await parseTxInfo(txList[1]); @@ -149,13 +157,17 @@ Then(/^I verify top transaction content ([^"]*)$/, async function (walletName) { const expectedTx = displayInfo[walletName]; const txId = await (async () => { - const elem = await topTx.findElement(By.css('.txid')); + const elem = await topTx.findElement( + getMethod(transactionIdText.method)(transactionIdText.locator) + ); return await elem.getText(); })(); const txConfirmation = status === 'successful' ? await (async () => { - const txConfirmationsCount = await topTx.findElement(By.css('.confirmationCount')); + const txConfirmationsCount = await topTx.findElement( + getMethod(confirmationCountText.method)(confirmationCountText.locator) + ); const txConfirmationParentElem = await txConfirmationsCount.findElement(By.xpath('./..')); return await txConfirmationParentElem.getText(); })() @@ -180,7 +192,9 @@ Then( await this.waitForElement(transactionComponent); const actualTxsList = await this.getElementsBy(transactionComponent); const topTx = actualTxsList[0]; - const assuranceElem = await topTx.findElements(By.css('.confirmationCount')); + const assuranceElem = await topTx.findElements( + getMethod(confirmationCountText.method)(confirmationCountText.locator) + ); const confirmationCount = await assuranceElem[0].getText(); expect(confirmationCount).to.equal(count); }); From 9f74865f9d44ac04dd0e0efea71e3030adc815d4 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 24 Oct 2022 14:53:41 +0300 Subject: [PATCH 152/199] Rearranged steps --- .../features/tx-history.feature | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/yoroi-extension/features/tx-history.feature b/packages/yoroi-extension/features/tx-history.feature index 498164e58e..c66ae8da30 100644 --- a/packages/yoroi-extension/features/tx-history.feature +++ b/packages/yoroi-extension/features/tx-history.feature @@ -4,21 +4,6 @@ Feature: Txs History Given I have opened the extension And I have completed the basic setup - @it-101 - Scenario: Open the tx history of an empty wallet (IT-101) - Given There is a Byron wallet stored named empty-wallet - When I see the transactions summary - Then I should see that the number of transactions is 0 - And I should see no transactions - - @it-102 - Scenario: Open the tx history of a simple wallet (IT-102) - Given There is a Byron wallet stored named simple-pending-wallet - When I see the transactions summary - Then I should see that the number of transactions is 3 - And I should see 2 pending transactions - And I should see 1 successful transactions - @it-56 Scenario: Check content of successful transaction (IT-56) Given There is a Byron wallet stored named many-tx-wallet @@ -43,6 +28,30 @@ Feature: Txs History Given I sleep for 1500 Then I verify top transaction content failed-single-tx + @it-96 + Scenario: Tx from other client updates tx history (IT-96) + Given There is a Byron wallet stored named many-tx-wallet + Given I see the transactions summary + Then A successful tx gets sent from my wallet from another client + Then I see the transactions summary + And I should see that the number of transactions is 7 + Then I should see the balance number "4.290005 ADA" + + @it-101 + Scenario: Open the tx history of an empty wallet (IT-101) + Given There is a Byron wallet stored named empty-wallet + When I see the transactions summary + Then I should see that the number of transactions is 0 + And I should see no transactions + + @it-102 + Scenario: Open the tx history of a simple wallet (IT-102) + Given There is a Byron wallet stored named simple-pending-wallet + When I see the transactions summary + Then I should see that the number of transactions is 3 + And I should see 2 pending transactions + And I should see 1 successful transactions + @it-103 Scenario: Open the tx history of a complex wallet (IT-103) Given There is a Byron wallet stored named many-tx-wallet @@ -65,6 +74,17 @@ Feature: Txs History Then I see the transactions summary And I should see that the number of transactions is 3 + @it-121 @TestAssuranceChain + Scenario: Number of confirmation increases overt time (IT-121) + Given There is a Byron wallet stored named small-single-tx + Given I see the transactions summary + Then I should see that the number of transactions is 1 + Then I expand the top transaction + And The number of confirmations of the top tx is 199 + Then A successful tx gets sent from my wallet from another client + Given I sleep for 1000 + And The number of confirmations of the top tx is 201 + @it-139 Scenario: Open the tx history of an already loaded wallet (IT-139) Given There is a Byron wallet stored named simple-pending-wallet @@ -78,23 +98,3 @@ Feature: Txs History Then I navigate to wallet sidebar category # should be same number of transactions after the resync And I should see that the number of transactions is 3 - - @it-96 - Scenario: Tx from other client updates tx history (IT-96) - Given There is a Byron wallet stored named many-tx-wallet - Given I see the transactions summary - Then A successful tx gets sent from my wallet from another client - Then I see the transactions summary - And I should see that the number of transactions is 7 - Then I should see the balance number "2.290005 ADA" - - @it-121 @TestAssuranceChain - Scenario: Number of confirmation increases overt time (IT-121) - Given There is a Byron wallet stored named small-single-tx - Given I see the transactions summary - Then I should see that the number of transactions is 1 - Then I expand the top transaction - And The number of confirmations of the top tx is 199 - Then A successful tx gets sent from my wallet from another client - Given I sleep for 1000 - And The number of confirmations of the top tx is 201 From 394c8bf31b6988929c85472aebfeb393ce96302f Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 24 Oct 2022 14:59:18 +0300 Subject: [PATCH 153/199] Ledger devices are not supported in tests anymore --- .../features/dashboard.feature | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/packages/yoroi-extension/features/dashboard.feature b/packages/yoroi-extension/features/dashboard.feature index ec7a0d74f8..d816b7aa47 100644 --- a/packages/yoroi-extension/features/dashboard.feature +++ b/packages/yoroi-extension/features/dashboard.feature @@ -25,26 +25,6 @@ Feature: Yoroi delegation dashboard Given The expected transaction is "hKYAgYJYIDZ36Gx7ppmv3BzVfULyRvhvaa79dgJQBqx4MT+tK7ohAAGBglg5AfiMMmOcqBWRIjRN6CEig4T8YKJcOWtIDaUVnSFW21zUiJyEEQ1N6QwNUDtRuETbPm/YeZEjiZW7GgCV8hsCGgACpGUDGhH+lM0EgYIBggBYHFbbXNSInIQRDU3pDA1QO1G4RNs+b9h5kSOJlbsFoVgd4VbbXNSInIQRDU3pDA1QO1G4RNs+b9h5kSOJlbsaAExLQKEAgoJYIDHFsozgC4AMMNymh4uSd8Xls6VSRnf9Dxv6kiJPzsubWEAXpQuoGfhAzvgfp0H9ouqVNr4ZQPpQnFG9frwUkkyzA7dLIl1GmIuFbkJFMp3AakfKpXSZ9s+3dpaw9hYFkKgLglgg6cWNnhkPKitPspqy3T6+Lqi2VU1F/s8JE36FUprlBHBYQICDQmLn20i7qEzQSnFGhJv3Yp2qiAFF/6XxaqOeIvva6u/jxDYC/CFoA3UV4B6thf4QFJZ9owY9EsOhQuu14A319g==" When I confirm Yoroi transfer funds Then I should see the dashboard screen - @it-157 - Scenario: User can withdraw Ledger rewards from the dashboard w/ deregister (IT-157) - Given I connected Ledger device 707fa118bf6b84 - When I select a Shelley-era Ledger device - And I restore the Ledger device - Then I should see the dashboard screen - Then I should see a plate DDBZ-0107 - And I have a wallet with funds - And I go to the dashboard screen - When I click on the withdraw button - Then I click on the checkbox - And I click the next button - And I see the deregistration for the transaction - Then I should see on the Yoroi withdrawal transfer summary screen: - | fromAddress | reward | fees | - | stake1u80tp0xvht8gv38vhwet36ulc7ntpeecp68sr86yxexaqcqnl9kg3 | 5 | 0.173157 | - Given The expected transaction is "hKYAgYJYIDaI6Gx7ppmv3BzVfULyRvhvaa79dgJQBqx4MT+tK7ohAAGBglg5AZ5LpnUkkQJc9L66AxowRB32isOtlx9ex+BXg9nesLzMus6GROy7srjrn8emsOc4Do8Bn0Q2TdBgGgCV8hsCGgACpGUDGhH+lM0EgYIBggBYHN6wvMy6zoZE7LuyuOufx6aw5zgOjwGfRDZN0GAFoVgd4d6wvMy6zoZE7LuyuOufx6aw5zgOjwGfRDZN0GAaAExLQKEAgoJYIEdh+F1Ly/RcgXQy3Br/Gb7zFANafCVSbGP6/E3DrheIWEA56EMm2Dh/LYVSt7LV0lWbNbGmdZZPKnlKnKgW9IoWgP6BXkUbrRK8tERZmohlMYF5FLBPVM4Z7bc6R5HAnIUEglgggM5Rjn5NshP7v7vfltH/FYGKXf5yG/BAPMnlkZqOsYBYQBwEPq1EtI/nUY5N7qfs3SMuiZyccmxQQCl0KigP757ebWOV3mMfjnK3APVRYkijl2+4Z1+OGElalELQUuxo1gz19g==" - When I confirm Yoroi transfer funds - Then I do not see the deregistration for the transaction - Then I should see the transactions screen @it-166 Scenario: Can unmangled from the dashboard (IT-166) From 1aab88609d41e1f41bc4e7edd9598d256b154738 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 24 Oct 2022 15:42:13 +0300 Subject: [PATCH 154/199] Unmangling was removed from the dashboard screen and left only on the Receive screen. --- .../features/dashboard.feature | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/yoroi-extension/features/dashboard.feature b/packages/yoroi-extension/features/dashboard.feature index d816b7aa47..1d19e00161 100644 --- a/packages/yoroi-extension/features/dashboard.feature +++ b/packages/yoroi-extension/features/dashboard.feature @@ -25,22 +25,3 @@ Feature: Yoroi delegation dashboard Given The expected transaction is "hKYAgYJYIDZ36Gx7ppmv3BzVfULyRvhvaa79dgJQBqx4MT+tK7ohAAGBglg5AfiMMmOcqBWRIjRN6CEig4T8YKJcOWtIDaUVnSFW21zUiJyEEQ1N6QwNUDtRuETbPm/YeZEjiZW7GgCV8hsCGgACpGUDGhH+lM0EgYIBggBYHFbbXNSInIQRDU3pDA1QO1G4RNs+b9h5kSOJlbsFoVgd4VbbXNSInIQRDU3pDA1QO1G4RNs+b9h5kSOJlbsaAExLQKEAgoJYIDHFsozgC4AMMNymh4uSd8Xls6VSRnf9Dxv6kiJPzsubWEAXpQuoGfhAzvgfp0H9ouqVNr4ZQPpQnFG9frwUkkyzA7dLIl1GmIuFbkJFMp3AakfKpXSZ9s+3dpaw9hYFkKgLglgg6cWNnhkPKitPspqy3T6+Lqi2VU1F/s8JE36FUprlBHBYQICDQmLn20i7qEzQSnFGhJv3Yp2qiAFF/6XxaqOeIvva6u/jxDYC/CFoA3UV4B6thf4QFJZ9owY9EsOhQuu14A319g==" When I confirm Yoroi transfer funds Then I should see the dashboard screen - - @it-166 - Scenario: Can unmangled from the dashboard (IT-166) - Given There is a Shelley wallet stored named shelley-mangled - And I have a wallet with funds - And I go to the dashboard screen - Then I should see the dashboard screen - When I click on the unmangle warning - Then I should see on the Yoroi transfer summary screen: - | recoveredBalance | fees | fromAddress | - | 10 | 0.165457 | addr1q8sm64ehfue7m7xrlh2zfu4uj9tn3z3yrzfdaly52gs667qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhzdk70 | - Given The expected transaction is "hKQAgYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgSugI11FwGBglg5ATFf/lO+USTb83qMl8g53oV7XmMSuklF3gfHb8kex9YZS/n0WTCduT05oFTkplWcw+TU0UvasV/VGgCWEC8CGgAChlEDGhH+lM2hAIGCWCCxG2517QHEmTBkk1BC3zBriToLyq4PxNikr8LCc0V+jFhA2GPUG8kGwqxTG/+UYJfKt4qoQn4rxUh6/Df2gd0L+imMJg9v6LfkiKGCXoNU/d3T971A7KbbTE94hcny6EpHB/X2" - And I enter the wallet password: - | password | - | asdfasdfasdf | - When I confirm Yoroi transfer funds - Then I do not see the deregistration for the transaction - Then I should see the summary screen - And I should see 1 pending transactions From 9538c10167cdd14090ed3a4fa3bd49b0075e3121 Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 25 Oct 2022 13:09:30 +0800 Subject: [PATCH 155/199] fix test mock server --- .../api/ada/lib/state-fetch/mockNetwork.js | 47 +++++++++++++------ .../features/mock-chain/mockCardanoServer.js | 17 +++++-- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js index a341bfabbb..a9d959b77d 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js @@ -19,7 +19,7 @@ import type { FilterUsedRequest, FilterUsedResponse, FilterFunc, } from '../../../common/lib/state-fetch/currencySpecificTypes'; import { RollbackApiError, } from '../../../common/errors'; -import { toEnterprise, addressToKind, toHexOrBase58, } from '../storage/bridge/utils'; +import { toEnterprise, addressToKind, toHexOrBase58 } from '../storage/bridge/utils'; import { CoreAddressTypes } from '../storage/database/primitives/enums'; import type { CoreAddressT } from '../storage/database/primitives/enums'; import { @@ -49,6 +49,15 @@ import type { } from '@emurgo/yoroi-lib-core/dist/utxo/models'; import { UtxoApiResult, } from '@emurgo/yoroi-lib-core/dist/utxo/models'; +function byronAddressToHex(byronAddrOrHex: string): string { + if (RustModule.WalletV4.ByronAddress.is_valid(byronAddrOrHex)) { + return Buffer.from( + RustModule.WalletV4.ByronAddress.from_base58(byronAddrOrHex).to_bytes() + ).toString('hex'); + } + return byronAddrOrHex; +} + /** convert bech32 address to bytes */ function fixAddresses( address: string, @@ -784,6 +793,8 @@ export class MockUtxoApi implements UtxoApiContract { async getUtxoAtPoint(req: UtxoAtPointRequest): Promise> { const { addresses, referenceBlockHash } = req; + const hexAddresses = addresses.map(a => fixAddresses(a, networks.CardanoMainnet)); + let lastTxIndex; for (lastTxIndex = this.blockchain.length - 1; lastTxIndex >= 0; lastTxIndex--) { const hash = this.blockchain[lastTxIndex].block_hash; @@ -809,8 +820,10 @@ export class MockUtxoApi implements UtxoApiContract { // add new for (let outputIndex = 0; outputIndex < tx.outputs.length; outputIndex++) { const output = tx.outputs[outputIndex]; - - if (!addresses.includes(output.address)) { + if (!( + hexAddresses.includes(byronAddressToHex(output.address)) || + hexAddresses.includes(Buffer.from(toEnterprise(output.address)?.to_address().to_bytes() || '').toString('hex')) + )) { continue; } @@ -843,6 +856,7 @@ export class MockUtxoApi implements UtxoApiContract { async getUtxoDiffSincePoint(req: UtxoDiffSincePointRequest): Promise> { const { addresses, untilBlockHash, afterBestBlock, } = req; + const hexAddresses = addresses.map(a => fixAddresses(a, networks.CardanoMainnet)); let seenUntilBlock = false; const utxoDiffItems = []; for (let i = this.blockchain.length - 1; i >= 0; i--) { @@ -861,7 +875,10 @@ export class MockUtxoApi implements UtxoApiContract { } tx.outputs.forEach((output, outputIndex) => { - if (!addresses.includes(output.address)) { + if (!( + hexAddresses.includes(byronAddressToHex(output.address)) || + hexAddresses.includes(Buffer.from(toEnterprise(output.address)?.to_address().to_bytes() || '').toString('hex')) + )) { return; } const utxoId = `${tx.hash}${outputIndex}` @@ -887,16 +904,18 @@ export class MockUtxoApi implements UtxoApiContract { } ); }); - tx.inputs.filter(input => addresses.includes(input.address)) - .forEach(input => { - utxoDiffItems.push( - ({ - type: 'input', - id: input.id, - amount: new BigNumber(input.amount), - }: UtxoDiffItem) - ); - }); + tx.inputs.filter(input => + hexAddresses.includes(byronAddressToHex(input.address)) || + hexAddresses.includes(Buffer.from(toEnterprise(input.address)?.to_address().to_bytes() || '').toString('hex')) + ).forEach(input => { + utxoDiffItems.push( + ({ + type: 'input', + id: input.id, + amount: new BigNumber(input.amount), + }: UtxoDiffItem) + ); + }); } } if (!seenUntilBlock) { diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js index 662409c47b..126fd0aa94 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js @@ -313,7 +313,7 @@ export function getMockServer(settings: { const bestBlockHash = await mockImporter.mockUtxoApi.getBestBlock(); const safeBlockHash = await mockImporter.mockUtxoApi.getSafeBlock(); res.send({ - safeBlocK: { hash: safeBlockHash }, + safeBlock: { hash: safeBlockHash }, bestBlock: { hash: bestBlockHash }, }); }); @@ -365,9 +365,14 @@ export function getMockServer(settings: { }); server.post('/api/v2/txs/utxoDiffSincePoint', async (req, res) => { - const { addresses, untilBlockHash, afterBestBlock } = req.body; + const { addresses, untilBlockHash, afterPoint } = req.body; const { result, value } = await mockImporter.mockUtxoApi.getUtxoDiffSincePoint( - { addresses, untilBlockHash, afterBestBlock } + { + addresses, + untilBlockHash, + // ignore itemIndex and txHash + afterBestBlock: afterPoint.blockHash + }, ); if (result !== 'SUCCESS') { res.status(500); @@ -392,7 +397,11 @@ export function getMockServer(settings: { tx_index: item.utxo.txIndex, }; }); - res.send({ diffItems }); + res.send({ + diffItems, + // no pagination, always return all at once + lastDiffPointSelected: { blockHash: untilBlockHash } + }); } }); From 98632fb5ec271ebb81cf4701a32f19467976da6c Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 25 Oct 2022 14:11:26 +0200 Subject: [PATCH 156/199] Fix eslint --- packages/yoroi-extension/features/pages/settingsPage.js | 6 +----- .../features/step_definitions/settings-ui-steps.js | 1 - packages/yoroi-extension/features/support/webdriver.js | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/yoroi-extension/features/pages/settingsPage.js b/packages/yoroi-extension/features/pages/settingsPage.js index 063d5b6c6a..50dc4d160a 100644 --- a/packages/yoroi-extension/features/pages/settingsPage.js +++ b/packages/yoroi-extension/features/pages/settingsPage.js @@ -148,8 +148,4 @@ export const explorerSettingsDropdown: LocatorObject = { export const cardanoPaymentsURLTitle: LocatorObject = { locator: "//h2[contains(text(), 'Cardano Payment URLs')]", method: 'xpath', -}; -export const currencyConversionText: LocatorObject = { - locator: "//h2[contains(text(), 'Currency Conversion')]", - method: 'xpath', -}; +}; \ No newline at end of file diff --git a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js index 16b372785c..a4b84ccf3a 100644 --- a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js @@ -24,7 +24,6 @@ import { logsTitle, reportingAProblemTitle, cardanoPaymentsURLTitle, - currencyConversionText, removeWalletButton, resyncWalletButton, exportButton, diff --git a/packages/yoroi-extension/features/support/webdriver.js b/packages/yoroi-extension/features/support/webdriver.js index 628eb221e3..ec459f0ce0 100644 --- a/packages/yoroi-extension/features/support/webdriver.js +++ b/packages/yoroi-extension/features/support/webdriver.js @@ -317,8 +317,8 @@ function CustomWorld(cmdInput: WorldInput) { (k, l, callback) => { window.yoroi.translations[l] .then(translation => callback(translation[k])) - // eslint-disable-next-line no-console .catch(e => { + // eslint-disable-next-line no-console console.error('Intl fail: ', e); }); }, From d1502d1fa86e7863f2a91f251d3fc88b0cd274d1 Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 25 Oct 2022 20:14:31 +0800 Subject: [PATCH 157/199] fix unit tests --- .../app/api/ada/lib/state-fetch/mockNetwork.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js index a9d959b77d..1780204144 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js @@ -793,7 +793,13 @@ export class MockUtxoApi implements UtxoApiContract { async getUtxoAtPoint(req: UtxoAtPointRequest): Promise> { const { addresses, referenceBlockHash } = req; - const hexAddresses = addresses.map(a => fixAddresses(a, networks.CardanoMainnet)); + const hexAddresses = addresses.map(a => { + const hex = fixAddresses(a, networks.CardanoMainnet); + if (hex.match(/^([a-f0-9][a-f0-9])+$/)) { + return hex; + } + return byronAddressToHex(a); + }); let lastTxIndex; for (lastTxIndex = this.blockchain.length - 1; lastTxIndex >= 0; lastTxIndex--) { @@ -856,7 +862,14 @@ export class MockUtxoApi implements UtxoApiContract { async getUtxoDiffSincePoint(req: UtxoDiffSincePointRequest): Promise> { const { addresses, untilBlockHash, afterBestBlock, } = req; - const hexAddresses = addresses.map(a => fixAddresses(a, networks.CardanoMainnet)); + const hexAddresses = addresses.map(a => { + const hex = fixAddresses(a, networks.CardanoMainnet); + if (hex.match(/^([a-f0-9][a-f0-9])+$/)) { + return hex; + } + return byronAddressToHex(a); + }); + let seenUntilBlock = false; const utxoDiffItems = []; for (let i = this.blockchain.length - 1; i >= 0; i--) { From 19f434ccc3127b030743249c96d7647432c1b7d9 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 25 Oct 2022 14:29:12 +0200 Subject: [PATCH 158/199] Fix lint --- packages/yoroi-extension/features/pages/settingsPage.js | 6 +----- .../features/step_definitions/settings-ui-steps.js | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/yoroi-extension/features/pages/settingsPage.js b/packages/yoroi-extension/features/pages/settingsPage.js index 063d5b6c6a..50dc4d160a 100644 --- a/packages/yoroi-extension/features/pages/settingsPage.js +++ b/packages/yoroi-extension/features/pages/settingsPage.js @@ -148,8 +148,4 @@ export const explorerSettingsDropdown: LocatorObject = { export const cardanoPaymentsURLTitle: LocatorObject = { locator: "//h2[contains(text(), 'Cardano Payment URLs')]", method: 'xpath', -}; -export const currencyConversionText: LocatorObject = { - locator: "//h2[contains(text(), 'Currency Conversion')]", - method: 'xpath', -}; +}; \ No newline at end of file diff --git a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js index 16b372785c..a4b84ccf3a 100644 --- a/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js +++ b/packages/yoroi-extension/features/step_definitions/settings-ui-steps.js @@ -24,7 +24,6 @@ import { logsTitle, reportingAProblemTitle, cardanoPaymentsURLTitle, - currencyConversionText, removeWalletButton, resyncWalletButton, exportButton, From 527b328c30a088b67d99ccfc76f28fbf34bd3df5 Mon Sep 17 00:00:00 2001 From: yushi Date: Tue, 25 Oct 2022 21:19:52 +0800 Subject: [PATCH 159/199] fix remove wallet --- .../lib/storage/bridge/walletBuilder/remove.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/walletBuilder/remove.js b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/walletBuilder/remove.js index f8111893df..a7b3072fcf 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/bridge/walletBuilder/remove.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/bridge/walletBuilder/remove.js @@ -40,7 +40,10 @@ import { import { GetDerivationSpecific, } from '../../database/walletTypes/common/api/read'; import { rawGetAddressRowsForWallet } from '../traitUtils'; import { isCardanoHaskell, isJormungandr, isErgo } from '../../database/prepackaged/networks'; - +import { + ModifyUtxoAtSafePoint, + ModifyUtxoDiffToBestBlock, +} from '../../database/utxo/api/write'; export async function removePublicDeriver(request: {| publicDeriver: IPublicDeriver<>, @@ -65,6 +68,8 @@ export async function removePublicDeriver(request: {| FreeBlocks, GetCertificates, ModifyTokenList, + ModifyUtxoAtSafePoint, + ModifyUtxoDiffToBestBlock, }); const db = request.publicDeriver.getDb(); const depTables = Object @@ -177,6 +182,17 @@ export async function removePublicDeriver(request: {| walletAddressIds ); } + + // 3) remove utxos + await ModifyUtxoAtSafePoint.remove( + db, dbTx, + request.publicDeriver.getPublicDeriverId() + ); + await ModifyUtxoDiffToBestBlock.removeAll( + db, dbTx, + request.publicDeriver.getPublicDeriverId() + ); + await deps.RemovePublicDeriver.remove( db, dbTx, { publicDeriverId: request.publicDeriver.getPublicDeriverId() } From 8938202f83a4f6b6d2ba7dc5bf682436230c8a72 Mon Sep 17 00:00:00 2001 From: Justabot Date: Tue, 25 Oct 2022 08:47:53 -0500 Subject: [PATCH 160/199] CS - Adding protocol to url to get tracking to work. --- packages/yoroi-extension/app/api/analytics/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/api/analytics/index.js b/packages/yoroi-extension/app/api/analytics/index.js index 73f063a9bd..a77ce0b4da 100644 --- a/packages/yoroi-extension/app/api/analytics/index.js +++ b/packages/yoroi-extension/app/api/analytics/index.js @@ -160,7 +160,7 @@ function emitEvent(instanceId: string, event: string): void { idsite: SITE_ID, rec: '1', action_name: (isTestnet ? 'testnet/' : '') + event, - url: `yoroi.extension/${isTestnet ? 'testnet/' : ''}${event}`, + url: `http://yoroi.extension/${isTestnet ? 'testnet/' : ''}${event}`, _id: INSTANCE_ID, rand: `${Date.now()}-${Math.random()}`, apiv: '1' From 5496755eb87cd3ff6df833b82dcd156124892315 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 25 Oct 2022 19:38:32 +0300 Subject: [PATCH 161/199] The expected balance has been changed --- packages/yoroi-extension/features/yoroi-transfer.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/features/yoroi-transfer.feature b/packages/yoroi-extension/features/yoroi-transfer.feature index aaa8af8dd2..6bdb624d0d 100644 --- a/packages/yoroi-extension/features/yoroi-transfer.feature +++ b/packages/yoroi-extension/features/yoroi-transfer.feature @@ -65,7 +65,7 @@ Feature: Transfer Yoroi Wallet funds Then I click the next button Then I should see on the Yoroi transfer summary screen: | fromAddress | recoveredBalance | fees | - | Ae2tdPwUPEZLcUx5AGMACPyLAuVXHisVyNBuiSk3Ru7qddYyn9ujDp1Ejwr | 3.110005 | 0.209237 | + | Ae2tdPwUPEZLcUx5AGMACPyLAuVXHisVyNBuiSk3Ru7qddYyn9ujDp1Ejwr | 6.110005 | 0.209237 | | Ae2tdPwUPEYzkKjrqPw1GHUty25Cj5fWrBVsWxiQYCxfoe2d9iLjTnt34Aj | | | | Ae2tdPwUPEZ5uzkzh1o2DHECiUi3iugvnnKHRisPgRRP3CTF4KCMvy54Xd3 | | | | Ae2tdPwUPEZJZPsFg8w5bXA4brfu8peYy5prmrFiYPACb7DX64iiBY8WvHD | | | @@ -76,7 +76,7 @@ Feature: Transfer Yoroi Wallet funds Then I should see wallet changed notice And I should see on the Yoroi transfer summary screen: | fromAddress | recoveredBalance | fees | - | Ae2tdPwUPEZLcUx5AGMACPyLAuVXHisVyNBuiSk3Ru7qddYyn9ujDp1Ejwr | 2.290005 | 0.201625 | + | Ae2tdPwUPEZLcUx5AGMACPyLAuVXHisVyNBuiSk3Ru7qddYyn9ujDp1Ejwr | 4.290005 | 0.201625 | | Ae2tdPwUPEYzkKjrqPw1GHUty25Cj5fWrBVsWxiQYCxfoe2d9iLjTnt34Aj | | | | Ae2tdPwUPEZJZPsFg8w5bXA4brfu8peYy5prmrFiYPACb7DX64iiBY8WvHD | | | | Ae2tdPwUPEZHG9AGUYWqFcM5zFn74qdEx2TqyZxuU68CQ33EBodWAVJ523w | | | From 6639ce659bae11e1ba9349fb4742b6eca2159398 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 25 Oct 2022 19:44:25 +0300 Subject: [PATCH 162/199] The expected fees has been changed --- packages/yoroi-extension/features/yoroi-transfer.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/yoroi-transfer.feature b/packages/yoroi-extension/features/yoroi-transfer.feature index 6bdb624d0d..e4fbc151ee 100644 --- a/packages/yoroi-extension/features/yoroi-transfer.feature +++ b/packages/yoroi-extension/features/yoroi-transfer.feature @@ -146,7 +146,7 @@ Feature: Transfer Yoroi Wallet funds Then I click the next button Then I should see on the Yoroi withdrawal transfer summary screen: | fromAddress | reward | fees | - | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.173157 | + | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.178877 | And I see the deregistration for the transaction And I enter the wallet password: | password | From 842a9feb4c9c2c8ea8fe96675aa3d4f7664e4613 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 25 Oct 2022 20:11:23 +0300 Subject: [PATCH 163/199] The expected fees has been changed --- .../yoroi-extension/features/yoroi-transfer.feature | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/yoroi-extension/features/yoroi-transfer.feature b/packages/yoroi-extension/features/yoroi-transfer.feature index e4fbc151ee..4eecd4252d 100644 --- a/packages/yoroi-extension/features/yoroi-transfer.feature +++ b/packages/yoroi-extension/features/yoroi-transfer.feature @@ -171,7 +171,7 @@ Feature: Transfer Yoroi Wallet funds Then I click the next button Then I should see on the Yoroi withdrawal transfer summary screen: | fromAddress | recoveredBalance | fees | - | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.171573 | + | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.177293 | And I do not see the deregistration for the transaction And I enter the wallet password: | password | @@ -193,7 +193,7 @@ Feature: Transfer Yoroi Wallet funds And I proceed with the recovery Then I should see on the Yoroi withdrawal transfer summary screen: | fromAddress | recoveredBalance | fees | - | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.171573 | + | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.177293 | And I do not see the deregistration for the transaction And I enter the wallet password: | password | @@ -215,7 +215,7 @@ Feature: Transfer Yoroi Wallet funds And I proceed with the recovery Then I should see on the Yoroi withdrawal transfer summary screen: | fromAddress | recoveredBalance | fees | - | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.171573 | + | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.177293 | And I do not see the deregistration for the transaction And I enter the wallet password: | password | @@ -237,7 +237,7 @@ Feature: Transfer Yoroi Wallet funds And I proceed with the recovery Then I should see on the Yoroi withdrawal transfer summary screen: | fromAddress | recoveredBalance | fees | - | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.171573 | + | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.177293 | And I do not see the deregistration for the transaction And I enter the wallet password: | password | @@ -259,7 +259,7 @@ Feature: Transfer Yoroi Wallet funds And I proceed with the recovery Then I should see on the Yoroi withdrawal transfer summary screen: | fromAddress | recoveredBalance | fees | - | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.171573 | + | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.177293 | And I do not see the deregistration for the transaction And I enter the wallet password: | password | @@ -285,7 +285,7 @@ Feature: Transfer Yoroi Wallet funds Then I click the next button Then I should see on the Yoroi withdrawal transfer summary screen: | fromAddress | recoveredBalance | fees | - | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.171573 | + | stake1ux2436tfe25727kul3qtnyr7k72rvw6ep7h59ll53suwhzq05v5j9 | 5 | 0.177293 | And I do not see the deregistration for the transaction And I enter the wallet password: | password | From d603b2e028ced68b1d4171c2b320ab9d7bc32602 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 25 Oct 2022 20:12:16 +0300 Subject: [PATCH 164/199] Using `expect` instead of `waitUntilText` --- .../features/support/helpers/transfer-helpers.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/yoroi-extension/features/support/helpers/transfer-helpers.js b/packages/yoroi-extension/features/support/helpers/transfer-helpers.js index 21fbb07764..588548623a 100644 --- a/packages/yoroi-extension/features/support/helpers/transfer-helpers.js +++ b/packages/yoroi-extension/features/support/helpers/transfer-helpers.js @@ -1,6 +1,7 @@ // @flow import BigNumber from 'bignumber.js'; +import { expect } from 'chai'; import { truncateAddress } from '../../../app/utils/formatters'; import { networks, @@ -77,10 +78,14 @@ export async function checkTotalAmountIsCorrect( const decimalPlaces = assetInfo.Metadata.numberOfDecimals; const ticker = getTokenName(assetInfo); const amountPerUnit = new BigNumber(10).pow(decimalPlaces); - const totalAmountFormatted = `${totalAmount + const expectedTotalAmountFormatted = `${totalAmount .dividedBy(amountPerUnit) .toFormat(decimalPlaces)} ${ticker}`; - await world.waitUntilText(amountField, totalAmountFormatted); + + await world.waitForElement(amountField); + const realAmountText = await world.getText(amountField); + + expect(expectedTotalAmountFormatted).to.be.equal(realAmountText); } export async function checkFinalBalanceIsCorrect( @@ -99,7 +104,10 @@ export async function checkFinalBalanceIsCorrect( const network = networks.CardanoMainnet; const assetInfo = defaultAssets.filter(asset => asset.NetworkId === network.NetworkId)[0]; const ticker = getTokenName(assetInfo); - const finalBalance = `${finalAmount} ${ticker}`; + const expectedBalance = `${finalAmount} ${ticker}`; + + await world.waitForElement(totalAmountField); + const realFinalBalanceText = await world.getText(totalAmountField); - await world.waitUntilText(totalAmountField, finalBalance); + expect(expectedBalance).to.be.equal(realFinalBalanceText); } From d387c1ce9f5cf8c52561678e3f991c454fdab4ce Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 25 Oct 2022 21:04:52 +0300 Subject: [PATCH 165/199] Rearranged scenarios in the feature --- .../features/yoroi-transfer.feature | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/yoroi-extension/features/yoroi-transfer.feature b/packages/yoroi-extension/features/yoroi-transfer.feature index 4eecd4252d..603aac5fd8 100644 --- a/packages/yoroi-extension/features/yoroi-transfer.feature +++ b/packages/yoroi-extension/features/yoroi-transfer.feature @@ -7,20 +7,29 @@ Feature: Transfer Yoroi Wallet funds And I navigate back to the main page Then I should see the Create wallet screen - @it-114 - Scenario: Yoroi transfer fails when user transfers from an empty wallet (IT-114) + @it-82 + Scenario: User can transfer funds from another Yoroi paper wallet (IT-82) + # The recovery phrase and its balance(s) are defined in + # /features/mock-chain/TestWallets.js and + # /features/mock-chain/mockImporter.js Given There is a Byron wallet stored named empty-wallet And I am on the transfer start screen When I click on the byron button on the transfer screen When I click on the icarus tab - Then I select the Byron 15-word option + Then I select the yoroi paper wallet option And I enter the recovery phrase: - | recoveryPhrase | - | remind style lunch result accuse upgrade atom eight limit glance frequent eternal fashion borrow monster | + | recoveryPhrase | + | mushroom expose slogan wagon uphold train absurd fix snake unable rescue curious escape member resource garbage enemy champion airport matrix year | + And I enter the paper wallet password "cool password" And I proceed with the recovery - Then I should see a plate XJOD-1073 + Then I should see a plate KOTZ-1730 Then I click the next button - Then I should see the Yoroi transfer error screen + Then I should see on the Yoroi transfer summary screen: + | fromAddress | recoveredBalance | fees | + | Ae2tdPwUPEZ7TQpzbJZCbA5BjW4zWYFn47jKo43ouvfe4EABoCfvEjwYvJr | 2 | 0.166425 | + Given The expected transaction is "hKQAgYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgSugI11CwGBglgrgtgYWCGDWBwoHaM1MYhgfLim8H41W1iW2HF0vHYAe5aqHlfRoAAaC5i61BoAG/pnAhoAAooZAxoR/pTNoQKBhFgg18n9dDSQAQOaCZv45nr6Siugpa1UECrB4PoeJf11D51YQH7FvLGwOx0vFm9SpYsA8qBlv3LmHh+Q/oQ82FRjrfcUdQEKy/1/oEX+8K/2CN+n8lKnouQovKGJNw0qfTaBbQBYIHos8jfmh7thEmh9Z4iWlGgVLpvfY779btjkhmBoW8dQQaD19g==" + When I confirm Yoroi transfer funds + Then I should see the Yoroi transfer success screen @it-111 Scenario: User can transfer funds from another Yoroi wallet (IT-111) @@ -85,47 +94,38 @@ Feature: Transfer Yoroi Wallet funds When I confirm Yoroi transfer funds Then I should see the Yoroi transfer success screen - @it-141 - Scenario: User can transfer funds from a ledger wallet (IT-141) + @it-114 + Scenario: Yoroi transfer fails when user transfers from an empty wallet (IT-114) Given There is a Byron wallet stored named empty-wallet And I am on the transfer start screen When I click on the byron button on the transfer screen When I click on the icarus tab - Then I select the ledger option + Then I select the Byron 15-word option And I enter the recovery phrase: - | recoveryPhrase | - | abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art | + | recoveryPhrase | + | remind style lunch result accuse upgrade atom eight limit glance frequent eternal fashion borrow monster | And I proceed with the recovery - Then I should see a plate JSKA-2258 + Then I should see a plate XJOD-1073 Then I click the next button - Then I should see on the Yoroi transfer summary screen: - | fromAddress | recoveredBalance | fees | - | Ae2tdPwUPEYyHfxoQYGPhyHuAfLHKfLubzo4kxyw2XDnLsLmACtjufaBs33 | 1.638497 | 0.166425 | - Given The expected transaction is "hKQAgYJYIBZt/eWxg7fglIOvu/zntB59b+00tAXMEEG0XyfosF1HAAGBglgrgtgYWCGDWBwoHaM1MYhgfLim8H41W1iW2HF0vHYAe5aqHlfRoAAaC5i61BoAFnZIAhoAAooZAxoR/pTNoQKBhFgg8elBZl4BQGla+Tli4OXssZO+OKu0a4uE90ZUlPqOjApYQIBH5jupap+Jngy81DdvR6P5lYpNTID5wTdO4zD/9r5LakFD8ibRUngv/Y83/2BPL7LZkgSzl3DydaxhllJ26A1YINNT5ssGb8HUyAOT9k5hdlJG+NKzPSnWME87YabidYnjQaD19g==" - When I confirm Yoroi transfer funds - Then I should see the Yoroi transfer success screen + Then I should see the Yoroi transfer error screen - @it-82 - Scenario: User can transfer funds from another Yoroi paper wallet (IT-82) - # The recovery phrase and its balance(s) are defined in - # /features/mock-chain/TestWallets.js and - # /features/mock-chain/mockImporter.js + @it-141 + Scenario: User can transfer funds from a ledger wallet (IT-141) Given There is a Byron wallet stored named empty-wallet And I am on the transfer start screen When I click on the byron button on the transfer screen When I click on the icarus tab - Then I select the yoroi paper wallet option + Then I select the ledger option And I enter the recovery phrase: | recoveryPhrase | - | mushroom expose slogan wagon uphold train absurd fix snake unable rescue curious escape member resource garbage enemy champion airport matrix year | - And I enter the paper wallet password "cool password" + | abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art | And I proceed with the recovery - Then I should see a plate KOTZ-1730 + Then I should see a plate JSKA-2258 Then I click the next button Then I should see on the Yoroi transfer summary screen: | fromAddress | recoveredBalance | fees | - | Ae2tdPwUPEZ7TQpzbJZCbA5BjW4zWYFn47jKo43ouvfe4EABoCfvEjwYvJr | 2 | 0.166425 | - Given The expected transaction is "hKQAgYJYILcTzA1jEGw4BrWnB3zDeilPzKDkefJqrGTlHgSugI11CwGBglgrgtgYWCGDWBwoHaM1MYhgfLim8H41W1iW2HF0vHYAe5aqHlfRoAAaC5i61BoAG/pnAhoAAooZAxoR/pTNoQKBhFgg18n9dDSQAQOaCZv45nr6Siugpa1UECrB4PoeJf11D51YQH7FvLGwOx0vFm9SpYsA8qBlv3LmHh+Q/oQ82FRjrfcUdQEKy/1/oEX+8K/2CN+n8lKnouQovKGJNw0qfTaBbQBYIHos8jfmh7thEmh9Z4iWlGgVLpvfY779btjkhmBoW8dQQaD19g==" + | Ae2tdPwUPEYyHfxoQYGPhyHuAfLHKfLubzo4kxyw2XDnLsLmACtjufaBs33 | 1.638497 | 0.166425 | + Given The expected transaction is "hKQAgYJYIBZt/eWxg7fglIOvu/zntB59b+00tAXMEEG0XyfosF1HAAGBglgrgtgYWCGDWBwoHaM1MYhgfLim8H41W1iW2HF0vHYAe5aqHlfRoAAaC5i61BoAFnZIAhoAAooZAxoR/pTNoQKBhFgg8elBZl4BQGla+Tli4OXssZO+OKu0a4uE90ZUlPqOjApYQIBH5jupap+Jngy81DdvR6P5lYpNTID5wTdO4zD/9r5LakFD8ibRUngv/Y83/2BPL7LZkgSzl3DydaxhllJ26A1YINNT5ssGb8HUyAOT9k5hdlJG+NKzPSnWME87YabidYnjQaD19g==" When I confirm Yoroi transfer funds Then I should see the Yoroi transfer success screen From 577521338dbbfdfc0ae4b68a0fd4abd0f998ddca Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 26 Oct 2022 12:34:44 +0300 Subject: [PATCH 166/199] Fixed locators --- .../features/pages/walletVotingPage.js | 24 +++++++++---------- .../features/step_definitions/voting-steps.js | 10 ++++---- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletVotingPage.js b/packages/yoroi-extension/features/pages/walletVotingPage.js index 155f39ae97..79512b5b79 100644 --- a/packages/yoroi-extension/features/pages/walletVotingPage.js +++ b/packages/yoroi-extension/features/pages/walletVotingPage.js @@ -3,31 +3,32 @@ import type { LocatorObject } from '../support/webdriver'; export const registerButton: LocatorObject = { - locator: '.Voting_registerButton > .primary', + locator: '.Voting_registerButton > .MuiButton-primary', method: 'css', }; + export const generatePinDialog: LocatorObject = { locator: '.GeneratePinDialog_dialog', method: 'css', }; -export const generatedPinButton: LocatorObject = { - locator: '.GeneratePinDialog_dialog > .Dialog_actions > .primary', - method: 'css', + +export const confirmButton: LocatorObject = { + locator: '//button[@id="primaryButton"]', + method: 'xpath', }; + export const generatedPinStepElement: LocatorObject = { locator: '.GeneratePinDialog_pin > span', method: 'css', }; + export const confirmPinDialog: LocatorObject = { locator: '.ConfirmPinDialog_dialog', method: 'css', }; export const pinInput: LocatorObject = { locator: "input[name='pin']", method: 'css' }; -export const confirmPinButton: LocatorObject = { - locator: '.ConfirmPinDialog_dialog > .Dialog_actions > .primary', - method: 'css', -}; + export const confirmPinDialogError: LocatorObject = { locator: '.ConfirmPinDialog_dialog .ConfirmPinDialog_pinInputContainer .FormFieldOverridesClassic_error', @@ -35,18 +36,17 @@ export const confirmPinDialogError: LocatorObject = { }; export const registerDialog: LocatorObject = { locator: '.RegisterDialog_dialog', method: 'css' }; -export const registerDialogNextButton: LocatorObject = { - locator: '.RegisterDialog_dialog > .Dialog_actions > .primary', - method: 'css', -}; export const votingRegTxDialog: LocatorObject = { locator: '.VotingRegTxDialog_dialog', method: 'css', }; + export const votingRegTxDialogError: LocatorObject = { locator: '.VotingRegTxDialog_error', method: 'css', }; + export const qrCodeDialog: LocatorObject = { locator: '.QrCodeDialog_dialog', method: 'css' }; + export const errorBlock: LocatorObject = { locator: '.ErrorBlock_component > span', method: 'css' }; diff --git a/packages/yoroi-extension/features/step_definitions/voting-steps.js b/packages/yoroi-extension/features/step_definitions/voting-steps.js index ab9330bec5..13a363b533 100644 --- a/packages/yoroi-extension/features/step_definitions/voting-steps.js +++ b/packages/yoroi-extension/features/step_definitions/voting-steps.js @@ -4,18 +4,16 @@ import { When, Then } from 'cucumber'; import { By, Key } from 'selenium-webdriver'; import { votingTab } from '../pages/walletPage'; import { - confirmPinButton, + confirmButton, confirmPinDialog, confirmPinDialogError, errorBlock, - generatedPinButton, generatedPinStepElement, generatePinDialog, pinInput, qrCodeDialog, registerButton, registerDialog, - registerDialogNextButton, votingRegTxDialog, votingRegTxDialogError, } from '../pages/walletVotingPage'; @@ -41,7 +39,7 @@ Then(/^I see the Auto generated Pin Steps$/, async function () { }); When(/^I click next on the generated pin step$/, async function () { - await this.click(generatedPinButton); + await this.click(confirmButton); }); Then(/^I see the confirm Pin step$/, async function () { @@ -54,7 +52,7 @@ Then(/^I enter the generated pin$/, async function () { }); When(/^I click next on the confirm pin step$/, async function () { - await this.click(confirmPinButton); + await this.click(confirmButton); }); Then(/^I see register step with spending password$/, async function () { @@ -62,7 +60,7 @@ Then(/^I see register step with spending password$/, async function () { }); When(/^I click next on the register step$/, async function () { - await this.click(registerDialogNextButton); + await this.click(confirmButton); }); Then(/^I see confirm transaction step$/, async function () { From 7d351480cc5f64d538ba56e895907a6a5a4d486c Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 26 Oct 2022 13:06:49 +0300 Subject: [PATCH 167/199] Changed the way to check the error message --- .../yoroi-extension/features/pages/walletVotingPage.js | 5 ++--- .../features/step_definitions/voting-steps.js | 8 +++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletVotingPage.js b/packages/yoroi-extension/features/pages/walletVotingPage.js index 79512b5b79..408a2a10ea 100644 --- a/packages/yoroi-extension/features/pages/walletVotingPage.js +++ b/packages/yoroi-extension/features/pages/walletVotingPage.js @@ -30,9 +30,8 @@ export const confirmPinDialog: LocatorObject = { export const pinInput: LocatorObject = { locator: "input[name='pin']", method: 'css' }; export const confirmPinDialogError: LocatorObject = { - locator: - '.ConfirmPinDialog_dialog .ConfirmPinDialog_pinInputContainer .FormFieldOverridesClassic_error', - method: 'css', + locator: '//p[starts-with(@id, "pin-") and contains(@id, "-helper-text")]', + method: 'xpath', }; export const registerDialog: LocatorObject = { locator: '.RegisterDialog_dialog', method: 'css' }; diff --git a/packages/yoroi-extension/features/step_definitions/voting-steps.js b/packages/yoroi-extension/features/step_definitions/voting-steps.js index 13a363b533..97747e7fad 100644 --- a/packages/yoroi-extension/features/step_definitions/voting-steps.js +++ b/packages/yoroi-extension/features/step_definitions/voting-steps.js @@ -18,6 +18,7 @@ import { votingRegTxDialogError, } from '../pages/walletVotingPage'; import i18n from '../support/helpers/i18n-helpers'; +import { expect } from 'chai'; When(/^I go to the voting page$/, async function () { await this.click(votingTab); @@ -78,12 +79,13 @@ Then(/^I enter the wrong pin$/, async function () { }); Then(/^I see should see pin mismatch error$/, async function () { - const errorMessage = await i18n.formatMessage(this.driver, { + const expectedErrorMessage = await i18n.formatMessage(this.driver, { id: 'global.errors.pinDoesNotMatch', }); - // following selector is used as the error is deeply nested - await this.waitUntilText(confirmPinDialogError, errorMessage); + await this.waitForElement(confirmPinDialogError); + const realErrorMessage = await this.getText(confirmPinDialogError); + expect(realErrorMessage, 'The error message is different').to.be.equal(expectedErrorMessage); // clear the wrong pin at the end // we are doing backspace 4 times for pin length of 4 From 3ef1ff97a6e7e23a6182894f371d3445139d217e Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 26 Oct 2022 16:44:29 +0300 Subject: [PATCH 168/199] reworked the language selection helper --- .../features/installation-procedure.feature | 1 + .../features/pages/basicSetupPage.js | 5 ++ .../installation-procedure-steps.js | 3 +- .../step_definitions/select-language-steps.js | 9 ++-- .../helpers/language-selection-helpers.js | 49 ++++++++----------- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/packages/yoroi-extension/features/installation-procedure.feature b/packages/yoroi-extension/features/installation-procedure.feature index 6f30a107fe..08fed9b640 100644 --- a/packages/yoroi-extension/features/installation-procedure.feature +++ b/packages/yoroi-extension/features/installation-procedure.feature @@ -13,6 +13,7 @@ Feature: Installation procedure @it-51 Scenario: Terms of Use are not accepted if user didn’t confirm it and close/reload the browser page (IT-51) Given I have opened the extension + And I am on the language selection screen And I have selected English language Given I am on the "Terms of use" screen When I refresh the page diff --git a/packages/yoroi-extension/features/pages/basicSetupPage.js b/packages/yoroi-extension/features/pages/basicSetupPage.js index 47b17ec461..65b1edeb7f 100644 --- a/packages/yoroi-extension/features/pages/basicSetupPage.js +++ b/packages/yoroi-extension/features/pages/basicSetupPage.js @@ -25,6 +25,11 @@ export const continueButton: LocatorObject = { locator: '//button[text()="Continue"]', method: 'xpath', }; + +export const confirmSelectedLanguageButton: LocatorObject = { + locator: `${LANGUAGE_SELECTION_FORM} > .LanguageSelectionForm_centeredBox > .MuiButton-primary`, + method: 'css' +}; // ToS page export const termsOfUseComponent: LocatorObject = { locator: '.TermsOfUseForm_component', diff --git a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js index b38493a5a6..c1527aa297 100644 --- a/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js +++ b/packages/yoroi-extension/features/step_definitions/installation-procedure-steps.js @@ -10,7 +10,8 @@ Given(/^I am on the "Terms of use" screen$/, async function () { When(/^I click on "I agree with the terms of use" checkbox$/, async function () { this.webDriverLogger.info(`Step: I click on "I agree with the terms of use" checkbox`); - const checkbox = await getTosCheckbox(); + await this.waitForElement(termsOfUseComponent); + const checkbox = await getTosCheckbox(this); await checkbox.click(); }); diff --git a/packages/yoroi-extension/features/step_definitions/select-language-steps.js b/packages/yoroi-extension/features/step_definitions/select-language-steps.js index 273d0a564f..b61b9ec689 100644 --- a/packages/yoroi-extension/features/step_definitions/select-language-steps.js +++ b/packages/yoroi-extension/features/step_definitions/select-language-steps.js @@ -2,15 +2,16 @@ import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; -import languageSelection, { clickContinue } from '../support/helpers/language-selection-helpers'; +import { ensureLanguageIsSelected } from '../support/helpers/language-selection-helpers'; import { + confirmSelectedLanguageButton, japaneseLaguageSelection, languageSelectionForm, languageSelectionFromDropdown, } from '../pages/basicSetupPage'; Given(/^I have selected English language$/, async function () { - await languageSelection.ensureLanguageIsSelected(this, { language: 'en-US' }); + await ensureLanguageIsSelected(this, { language: 'en-US' }); }); When(/^I am on the language selection screen$/, async function () { @@ -22,11 +23,11 @@ When(/^I open language selection dropdown$/, async function () { }); When(/^I select Japanese language$/, async function () { - return this.click(japaneseLaguageSelection); + await this.click(japaneseLaguageSelection); }); When(/^I submit the language selection form$/, async function () { - await clickContinue(this); + await this.click(confirmSelectedLanguageButton); }); Then(/^I should not see the language selection screen anymore$/, async function () { diff --git a/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js b/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js index d22abd7cf9..19a7d09743 100644 --- a/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js +++ b/packages/yoroi-extension/features/support/helpers/language-selection-helpers.js @@ -1,36 +1,27 @@ // @flow import i18n from './i18n-helpers'; -import { By } from 'selenium-webdriver'; -import { languageSelectionForm } from '../../pages/basicSetupPage'; +import { + languageSelectionForm, + confirmSelectedLanguageButton +} from '../../pages/basicSetupPage'; -const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; - -const languageSelection = { - waitForVisible: async ( - client: any, - { isHidden }: {| isHidden: boolean, |} = {} - ): Promise => { - if (isHidden) { - return client.waitForElementNotPresent(languageSelectionForm); - } - return client.waitForElement(languageSelectionForm); - }, - ensureLanguageIsSelected: async ( - client: any, - { language }: {| language: string, |} = {} - ): Promise => { - await languageSelection.waitForVisible(client.driver); - await i18n.setActiveLanguage(client.driver, { language }); - await clickContinue(client); - await languageSelection.waitForVisible(client.driver, { isHidden: true }); +export const waitForVisibleLanguageSelection = async ( + customWorld: any, + { isHidden }: {| isHidden: boolean, |} = {} +): Promise => { + if (isHidden) { + return customWorld.waitForElementNotPresent(languageSelectionForm); } + return customWorld.waitForElement(languageSelectionForm); }; -export const clickContinue = async (world: Object) => { - const parentComponent = await world.driver.findElement(By.css(LANGUAGE_SELECTION_FORM)); - const continueButton = await parentComponent.findElement(By.xpath('//button')); - await continueButton.click(); -} - -export default languageSelection; +export const ensureLanguageIsSelected = async ( + customWorld: any, + { language }: {| language: string, |} +): Promise => { + await waitForVisibleLanguageSelection(customWorld); + await i18n.setActiveLanguage(customWorld.driver, { language }); + await customWorld.click(confirmSelectedLanguageButton); + await waitForVisibleLanguageSelection(customWorld, { isHidden: true }); +}; From 942bc9360ec87ac15ba276121de3f70212b7a245 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 26 Oct 2022 16:45:32 +0300 Subject: [PATCH 169/199] Rearranged scenarios --- .../features/installation-procedure.feature | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/yoroi-extension/features/installation-procedure.feature b/packages/yoroi-extension/features/installation-procedure.feature index 08fed9b640..ccb7d891fb 100644 --- a/packages/yoroi-extension/features/installation-procedure.feature +++ b/packages/yoroi-extension/features/installation-procedure.feature @@ -1,15 +1,5 @@ Feature: Installation procedure - @it-98 - Scenario: User Selects Language at first launch (IT-98) - Given I have opened the extension - And I am on the language selection screen - And I open language selection dropdown - And I select Japanese language - When I submit the language selection form - Then I should not see the language selection screen anymore - And I should have Japanese language set - @it-51 Scenario: Terms of Use are not accepted if user didn’t confirm it and close/reload the browser page (IT-51) Given I have opened the extension @@ -20,4 +10,14 @@ Feature: Installation procedure And I click on "I agree with the terms of use" checkbox When I submit the "Terms of use" form Then I should not see the "Terms of use" screen anymore - And I should have "Terms of use" accepted \ No newline at end of file + And I should have "Terms of use" accepted + + @it-98 + Scenario: User Selects Language at first launch (IT-98) + Given I have opened the extension + And I am on the language selection screen + And I open language selection dropdown + And I select Japanese language + When I submit the language selection form + Then I should not see the language selection screen anymore + And I should have Japanese language set \ No newline at end of file From 0697e8efce307eec5296c3e404c5947be6745ea6 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 26 Oct 2022 17:59:54 +0300 Subject: [PATCH 170/199] Updated fee --- packages/yoroi-extension/features/migration.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/migration.feature b/packages/yoroi-extension/features/migration.feature index 08c64e7961..c2a777b5cb 100644 --- a/packages/yoroi-extension/features/migration.feature +++ b/packages/yoroi-extension/features/migration.feature @@ -60,7 +60,7 @@ Feature: Migration Examples: | amount | fee | - | 1.000000 | 0.168845 | + | 1.000000 | 0.180065 | @it-140 Scenario: Upgrade from version that adds bip44 support (IT-140) From 49e648c41906d7bbf431104d025a1ff179396f30 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 26 Oct 2022 22:02:42 +0300 Subject: [PATCH 171/199] Changed locator for the memo --- .../yoroi-extension/features/pages/walletSendPage.js | 4 ++-- .../yoroi-extension/features/trezor-emulator.feature | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/yoroi-extension/features/pages/walletSendPage.js b/packages/yoroi-extension/features/pages/walletSendPage.js index 84495dd34c..af5f2d2c4c 100644 --- a/packages/yoroi-extension/features/pages/walletSendPage.js +++ b/packages/yoroi-extension/features/pages/walletSendPage.js @@ -36,8 +36,8 @@ export const getMemoText = async (customWorld: Object): Promise => { return await memoElem[0].getText(); }; export const memoContentInput: LocatorObject = { - locator: "input[name='memoContent']", - method: 'css', + locator: '//input[starts-with(@name, "memo")]', + method: 'xpath', }; export const editMemoButton: LocatorObject = { locator: '.editMemoButton', method: 'css' }; export const deleteMemo = async (customWorld: Object, confirmDeleting: boolean = true) => { diff --git a/packages/yoroi-extension/features/trezor-emulator.feature b/packages/yoroi-extension/features/trezor-emulator.feature index dd88f61045..ecbddca5fa 100644 --- a/packages/yoroi-extension/features/trezor-emulator.feature +++ b/packages/yoroi-extension/features/trezor-emulator.feature @@ -12,7 +12,7 @@ Feature: Trezor wallet emulator Then I should see a plate PXCA-2349 @Trezor-001 - Scenario: Trezor (emulator). Send ADA. + Scenario: Trezor (emulator). Send ADA. (Trezor-001) Given I go to the send transaction screen And I fill the form: | address | amount | @@ -29,7 +29,7 @@ Feature: Trezor wallet emulator Then I should see the summary screen @Trezor-002 - Scenario: Trezor (emulator). Verify address. + Scenario: Trezor (emulator). Verify address. (Trezor-002) When I go to the receive screen Given I should see the Receive screen And I click on the verify address button @@ -38,7 +38,7 @@ Feature: Trezor wallet emulator And I verify the address on the trezor emulator @Trezor-003 - Scenario: Trezor (emulator). Test Shelley Trezor delegation + Scenario: Trezor (emulator). Test Shelley Trezor delegation. (Trezor-003) When I go to the delegation by id screen And I fill the delegation id form: | stakePoolId | @@ -54,7 +54,7 @@ Feature: Trezor wallet emulator Then I should see the dashboard screen @Trezor-004 - Scenario: Trezor (emulator). Withdraw rewards w/ deregistration. + Scenario: Trezor (emulator). Withdraw rewards w/ deregistration. (Trezor-004) When I go to the dashboard screen And I click on the withdraw button And I click on the checkbox @@ -71,7 +71,7 @@ Feature: Trezor wallet emulator Then I should see the transactions screen @Trezor-005 - Scenario: Trezor (emulator). Send Assets. + Scenario: Trezor (emulator). Send Assets. (Trezor-005) Given I go to the send transaction screen And I select the asset "nicoin" on the form And I fill the form: @@ -89,7 +89,7 @@ Feature: Trezor wallet emulator Then I should see the summary screen @Trezor-006 - Scenario: Trezor (emulator). User can transfer funds from a trezor wallet + Scenario: Trezor (emulator). User can transfer funds from a trezor wallet. (Trezor-006) Given I switched to the advanced level And I am on the transfer start screen When I click on the byron button on the transfer screen From 6d4cf996367bcf530938ab8bfd9b6e75b29e9210 Mon Sep 17 00:00:00 2001 From: yushi Date: Thu, 27 Oct 2022 11:38:40 +0800 Subject: [PATCH 172/199] bump yoroi-lib version --- packages/yoroi-extension/.flowconfig | 1 + .../app/api/ada/lib/state-fetch/mockNetwork.js | 6 +++--- .../yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js | 4 ++-- .../app/api/ada/lib/storage/models/PublicDeriver/index.js | 2 +- .../api/ada/lib/storage/models/PublicDeriver/interfaces.js | 2 +- .../app/api/ada/lib/storage/models/PublicDeriver/traits.js | 2 +- .../app/api/ada/lib/storage/models/utils.js | 4 ++-- .../features/mock-chain/mockCardanoServer.js | 7 ++++--- packages/yoroi-extension/package.json | 2 +- 9 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/yoroi-extension/.flowconfig b/packages/yoroi-extension/.flowconfig index ffa74a6c38..1255df19c0 100644 --- a/packages/yoroi-extension/.flowconfig +++ b/packages/yoroi-extension/.flowconfig @@ -7,6 +7,7 @@ .*/.circleci/.* /dev/.* /build/.* +/node_modules/@emurgo/yoroi-lib/node_modules/resolve/test/resolver/malformed_package_json/package.json [include] ../node_modules/eslint-plugin-jsx-a11y diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js index 1780204144..460c2fb06d 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js @@ -37,7 +37,7 @@ import { networks, getCardanoHaskellBaseConfig } from '../storage/database/prepa import { bech32 } from 'bech32'; import { Bech32Prefix } from '../../../../config/stringConfig'; import { parseTokenList } from '../../transactions/utils'; -import type { UtxoApiContract } from '@emurgo/yoroi-lib-core/dist/utxo/api'; +import type { UtxoApiContract } from '@emurgo/yoroi-lib/dist/utxo/api'; import type { TipStatusReference, Utxo, @@ -46,8 +46,8 @@ import type { UtxoDiff, UtxoDiffItem, UtxoDiffSincePointRequest -} from '@emurgo/yoroi-lib-core/dist/utxo/models'; -import { UtxoApiResult, } from '@emurgo/yoroi-lib-core/dist/utxo/models'; +} from '@emurgo/yoroi-lib/dist/utxo/models'; +import { UtxoApiResult, } from '@emurgo/yoroi-lib/dist/utxo/models'; function byronAddressToHex(byronAddrOrHex: string): string { if (RustModule.WalletV4.ByronAddress.is_valid(byronAddrOrHex)) { diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js index 19e5355478..452de56bbd 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/utxoApi.js @@ -3,8 +3,8 @@ import axios from 'axios'; import { BatchedEmurgoUtxoApi, EmurgoUtxoApi -} from '@emurgo/yoroi-lib-core/dist/utxo/emurgo-api'; -import type { UtxoApiContract } from '@emurgo/yoroi-lib-core/dist/utxo/api'; +} from '@emurgo/yoroi-lib/dist/utxo/emurgo-api'; +import type { UtxoApiContract } from '@emurgo/yoroi-lib/dist/utxo/api'; export default class UtxoApi extends BatchedEmurgoUtxoApi { // so that the unit tests can override it with mocks diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js index 4ce3a975ce..5d7dc506e9 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js @@ -39,7 +39,7 @@ import { GetKeyDerivation, } from '../../database/primitives/api/read'; import { addTraitsForBip44Child, addTraitsForCip1852Child } from './traits'; -import { UtxoService } from '@emurgo/yoroi-lib-core/dist/utxo'; +import { UtxoService } from '@emurgo/yoroi-lib/dist/utxo'; import { UtxoStorageApi, } from '../utils'; import UtxoApi from '../../../state-fetch/utxoApi'; diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js index 38be008f45..30baf2caf6 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js @@ -68,7 +68,7 @@ import type { GetUtxoAtSafePoint, GetUtxoDiffToBestBlock, } from '../../database/utxo/api/read'; -import { UtxoService } from '@emurgo/yoroi-lib-core/dist/utxo'; +import { UtxoService } from '@emurgo/yoroi-lib/dist/utxo'; import { UtxoStorageApi, } from '../utils'; diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js index 95ad1473bb..65102f71b9 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/traits.js @@ -142,7 +142,7 @@ import { } from '../../database/utxo/api/read'; import type { Utxo, -} from '@emurgo/yoroi-lib-core/dist/utxo/models'; +} from '@emurgo/yoroi-lib/dist/utxo/models'; interface Empty {} type HasPrivateDeriverDependencies = IPublicDeriver; diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js index b9129e10dc..7530774357 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/utils.js @@ -14,12 +14,12 @@ import { BigNumber } from 'bignumber.js'; -import type { UtxoStorage } from '@emurgo/yoroi-lib-core/dist/utxo'; +import type { UtxoStorage } from '@emurgo/yoroi-lib/dist/utxo'; import type { Utxo, UtxoAtSafePoint, UtxoDiffToBestBlock -} from '@emurgo/yoroi-lib-core/dist/utxo/models'; +} from '@emurgo/yoroi-lib/dist/utxo/models'; import type { Utxo as StorageUtxo } from '../database/utxo/tables'; import type { diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js index 126fd0aa94..fe7c8b4edb 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js @@ -365,13 +365,14 @@ export function getMockServer(settings: { }); server.post('/api/v2/txs/utxoDiffSincePoint', async (req, res) => { - const { addresses, untilBlockHash, afterPoint } = req.body; + // ignore `blockCount` and returns all diff items at once + const { addresses, untilBlockHash, afterBlockHash, /* blockCount */ } = req.body; const { result, value } = await mockImporter.mockUtxoApi.getUtxoDiffSincePoint( { addresses, untilBlockHash, // ignore itemIndex and txHash - afterBestBlock: afterPoint.blockHash + afterBestBlock: afterBlockHash }, ); if (result !== 'SUCCESS') { @@ -400,7 +401,7 @@ export function getMockServer(settings: { res.send({ diffItems, // no pagination, always return all at once - lastDiffPointSelected: { blockHash: untilBlockHash } + lastBlockHash: untilBlockHash, }); } }); diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index e2e04b74bf..5539691590 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -185,7 +185,7 @@ "@emurgo/cip14-js": "2.0.0", "@emurgo/cip4-js": "1.0.5", "@emurgo/js-chain-libs": "0.7.1", - "@emurgo/yoroi-lib-core": "0.9.3-alpha.55", + "@emurgo/yoroi-lib": "0.1.3", "@mui/lab": "^5.0.0-alpha.51", "@mui/material": "^5.0.4", "@svgr/webpack": "5.5.0", From 257708dadcf4fa32bbfb7c792a80a48e56c17b9f Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 27 Oct 2022 17:48:46 +0300 Subject: [PATCH 173/199] Added waiter of a window title --- .../features/support/windowManager.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/support/windowManager.js b/packages/yoroi-extension/features/support/windowManager.js index 3715277dd4..c2b7e187c6 100644 --- a/packages/yoroi-extension/features/support/windowManager.js +++ b/packages/yoroi-extension/features/support/windowManager.js @@ -1,5 +1,6 @@ // @flow import { WebDriver } from 'selenium-webdriver'; +import { defaultRepeatPeriod, defaultWaitTimeout } from './helpers/common-constants'; type WindowType = 'tab' | 'window'; type CustomWindowHandle = {| @@ -36,9 +37,24 @@ export class WindowManager { this.windowHandles.push({ title: windowTitle, handle: mainWindowHandle }); } + async _waitWindowTitle( + timeoutMs: number = defaultWaitTimeout, + repeatPeriodMs: number = defaultRepeatPeriod): Promise { + this.logger.info(`WindowManager:_waitWindowTitle: Waiting for the window title`); + const endTime = Date.now() + timeoutMs; + + while (endTime >= Date.now()) { + const windowTitle = await this.driver.getTitle(); + if (windowTitle !== '') return windowTitle; + await this.driver.sleep(repeatPeriodMs); + } + this.logger.error(`WindowManager:_waitWindowTitle: -> The window has the empty title`); + throw new WindowManagerError(`The window has the empty title`); + } + async _getWindowTitle(): Promise { this.logger.info(`WindowManager: Getting a window title`); - const windowTitle = await this.driver.getTitle(); + const windowTitle = await this._waitWindowTitle(); this.logger.info(`WindowManager: -> The window title is "${windowTitle}"`); if (windowTitle === extensionTabName) { return extensionTabName; @@ -184,6 +200,7 @@ export class WindowManager { this.logger.info( `WindowManager: -> Switched to the new window ${JSON.stringify(popUpCustomHandle)}` ); + await this._waitWindowTitle(); return popUpCustomHandle; } From 7b99ae67c2ea2c23e8c5775d695b17143c914a3a Mon Sep 17 00:00:00 2001 From: yushi Date: Fri, 28 Oct 2022 00:27:53 +0800 Subject: [PATCH 174/199] fix mock server --- .../features/mock-chain/mockCardanoServer.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js index fe7c8b4edb..da294f92af 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js @@ -342,7 +342,7 @@ export function getMockServer(settings: { }); server.post('/api/v2/txs/utxoAtPoint', async (req, res) => { - const { addresses, referenceBlockHash } = req.body; + const { addresses, referenceBlockHash, page, pageSize } = req.body; const { value } = await mockImporter.mockUtxoApi.getUtxoAtPoint( { addresses, referenceBlockHash } ); @@ -350,7 +350,10 @@ export function getMockServer(settings: { throw new Error('unpected null value'); } res.send( - value.map(v => ( + value.slice( + (Number(page) - 1) * Number(pageSize), + Number(page) * Number(pageSize), + ).map(v => ( { utxo_id: v.utxoId, tx_hash: v.txHash, From 7bca40d6df55ae5f9ed4ead9c8b0d58d1b57ea07 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Mon, 31 Oct 2022 15:45:28 +0300 Subject: [PATCH 175/199] onload function fix --- packages/yoroi-ergo-connector/example-cardano/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-ergo-connector/example-cardano/index.js b/packages/yoroi-ergo-connector/example-cardano/index.js index c23346eede..af530b7e76 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.js +++ b/packages/yoroi-ergo-connector/example-cardano/index.js @@ -1002,7 +1002,7 @@ function toggleConnectionUI(status) { } } -window.onload = (() => { +window.onload = () => { if (typeof window.cardano === 'undefined') { alertError('Cardano API not found'); } else { @@ -1023,4 +1023,4 @@ window.onload = (() => { } ); } -})(); +}; From 6c1073fa8d258a7e9cd2beab7ed1250688b38bda Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 1 Nov 2022 13:29:51 +0200 Subject: [PATCH 176/199] Make privacy state toggleable --- .../app/actions/wallet-backup-actions.js | 2 +- .../app/components/wallet/WalletBackupDialog.js | 6 +++--- .../WalletBackupPrivacyWarningDialog.js | 6 +++--- .../app/containers/wallet/WalletAddPage.stories.js | 2 +- .../wallet/dialogs/WalletBackupDialogContainer.js | 10 +++++----- .../app/stores/toplevel/WalletBackupStore.js | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/yoroi-extension/app/actions/wallet-backup-actions.js b/packages/yoroi-extension/app/actions/wallet-backup-actions.js index 8e78ec2b2a..09cc9ef833 100644 --- a/packages/yoroi-extension/app/actions/wallet-backup-actions.js +++ b/packages/yoroi-extension/app/actions/wallet-backup-actions.js @@ -11,7 +11,7 @@ export default class WalletBackupActions { password: string, |}> = new Action(); continueToPrivacyWarning: Action = new Action(); - acceptPrivacyNoticeForWalletBackup: Action = new Action(); + togglePrivacyNoticeForWalletBackup: Action = new Action(); continueToRecoveryPhraseForWalletBackup: Action = new Action(); addWordToWalletBackupVerification: Action<{| word: string, index: number |}> = new Action(); clearEnteredRecoveryPhrase: Action = new Action(); diff --git a/packages/yoroi-extension/app/components/wallet/WalletBackupDialog.js b/packages/yoroi-extension/app/components/wallet/WalletBackupDialog.js index aa5d8ba453..8f5df08787 100644 --- a/packages/yoroi-extension/app/components/wallet/WalletBackupDialog.js +++ b/packages/yoroi-extension/app/components/wallet/WalletBackupDialog.js @@ -25,7 +25,7 @@ type Props = {| index: number, |}>, +onCancelBackup: void => void, - +onAcceptPrivacyNotice: void => void, + +onTogglePrivacyNotice: void => void, +onContinue: void => void, +onBack: void => void, +onStartWalletBackup: void => void, @@ -47,7 +47,7 @@ export default class WalletBackupDialog extends Component { const { currentStep, onCancelBackup, canPhraseBeShown, isPrivacyNoticeAccepted, - countdownRemaining, onAcceptPrivacyNotice, + countdownRemaining, togglePrivacyNotice, onContinue, onBack, recoveryPhrase, onStartWalletBackup, isTermDeviceAccepted, enteredPhrase, removeWord, hasWord, @@ -63,7 +63,7 @@ export default class WalletBackupDialog extends Component { canPhraseBeShown={canPhraseBeShown} isPrivacyNoticeAccepted={isPrivacyNoticeAccepted} countdownRemaining={countdownRemaining} - onAcceptPrivacyNotice={onAcceptPrivacyNotice} + togglePrivacyNotice={togglePrivacyNotice} onCancelBackup={onCancelBackup} onContinue={onContinue} classicTheme={classicTheme} diff --git a/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletBackupPrivacyWarningDialog.js b/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletBackupPrivacyWarningDialog.js index e7a3420250..cd666c9200 100644 --- a/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletBackupPrivacyWarningDialog.js +++ b/packages/yoroi-extension/app/components/wallet/backup-recovery/WalletBackupPrivacyWarningDialog.js @@ -30,7 +30,7 @@ type Props = {| +countdownRemaining: number, +canPhraseBeShown: boolean, +isPrivacyNoticeAccepted: boolean, - +onAcceptPrivacyNotice: void => void, + +togglePrivacyNotice: void => void, +onContinue: void => void, +onCancelBackup: void => void, +classicTheme: boolean @@ -48,7 +48,7 @@ export default class WalletBackupPrivacyWarningDialog extends Component { const { countdownRemaining, canPhraseBeShown, - onAcceptPrivacyNotice, + togglePrivacyNotice, onCancelBackup, isPrivacyNoticeAccepted, onContinue, @@ -85,7 +85,7 @@ export default class WalletBackupPrivacyWarningDialog extends Component {
    action('finishWalletBackup')(req), }, removeOneMnemonicWord: { trigger: action('removeOneMnemonicWord'), }, continueToPrivacyWarning: { trigger: action('continueToPrivacyWarning'), }, - acceptPrivacyNoticeForWalletBackup: { trigger: action('acceptPrivacyNoticeForWalletBackup'), }, + togglePrivacyNoticeForWalletBackup: { trigger: action('togglePrivacyNoticeForWalletBackup'), }, continueToRecoveryPhraseForWalletBackup: { trigger: action('continueToRecoveryPhraseForWalletBackup'), }, }, }, diff --git a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletBackupDialogContainer.js b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletBackupDialogContainer.js index 3659a548ca..7f60591294 100644 --- a/packages/yoroi-extension/app/containers/wallet/dialogs/WalletBackupDialogContainer.js +++ b/packages/yoroi-extension/app/containers/wallet/dialogs/WalletBackupDialogContainer.js @@ -49,7 +49,7 @@ export default class WalletBackupDialogContainer extends Component { finishWalletBackup, removeOneMnemonicWord, continueToPrivacyWarning, - acceptPrivacyNoticeForWalletBackup, + togglePrivacyNoticeForWalletBackup, continueToRecoveryPhraseForWalletBackup } = actions.walletBackup; const { createWalletRequest } = stores.wallets; @@ -63,7 +63,7 @@ export default class WalletBackupDialogContainer extends Component { canPhraseBeShown={isPrivacyNoticeAccepted && countdownRemaining === 0} isPrivacyNoticeAccepted={isPrivacyNoticeAccepted} countdownRemaining={countdownRemaining} - onAcceptPrivacyNotice={acceptPrivacyNoticeForWalletBackup.trigger} + togglePrivacyNotice={togglePrivacyNoticeForWalletBackup.trigger} onBack={continueToPrivacyWarning.trigger} onContinue={continueToRecoveryPhraseForWalletBackup.trigger} // Props for WalletRecoveryPhraseDisplayDialog @@ -94,7 +94,7 @@ export default class WalletBackupDialogContainer extends Component { @computed get generated(): {| actions: {| walletBackup: {| - acceptPrivacyNoticeForWalletBackup: {| + togglePrivacyNoticeForWalletBackup: {| trigger: (params: void) => void |}, acceptWalletBackupTermDevice: {| @@ -217,8 +217,8 @@ export default class WalletBackupDialogContainer extends Component { continueToPrivacyWarning: { trigger: actions.walletBackup.continueToPrivacyWarning.trigger, }, - acceptPrivacyNoticeForWalletBackup: { - trigger: actions.walletBackup.acceptPrivacyNoticeForWalletBackup.trigger, + togglePrivacyNoticeForWalletBackup: { + trigger: actions.walletBackup.togglePrivacyNoticeForWalletBackup.trigger, }, continueToRecoveryPhraseForWalletBackup: { trigger: actions.walletBackup.continueToRecoveryPhraseForWalletBackup.trigger, diff --git a/packages/yoroi-extension/app/stores/toplevel/WalletBackupStore.js b/packages/yoroi-extension/app/stores/toplevel/WalletBackupStore.js index 6da9d5c09f..dcb9b19348 100644 --- a/packages/yoroi-extension/app/stores/toplevel/WalletBackupStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/WalletBackupStore.js @@ -49,7 +49,7 @@ class WalletBackupStore extends Store { const a = this.actions.walletBackup; a.initiateWalletBackup.listen(this._initiateWalletBackup); a.continueToPrivacyWarning.listen(this._continueToPrivacyWarning); - a.acceptPrivacyNoticeForWalletBackup.listen(this._acceptPrivacyNoticeForWalletBackup); + a.togglePrivacyNoticeForWalletBackup.listen(this._togglePrivacyNoticeForWalletBackup); a.continueToRecoveryPhraseForWalletBackup.listen(this._continueToRecoveryPhraseForWalletBackup); a.startWalletBackup.listen(this._startWalletBackup); a.addWordToWalletBackupVerification.listen(this._addWordToWalletBackupVerification); @@ -100,8 +100,8 @@ class WalletBackupStore extends Store { this.currentStep = 'privacyWarning'; }; - @action _acceptPrivacyNoticeForWalletBackup: void => void = () => { - this.isPrivacyNoticeAccepted = true; + @action _togglePrivacyNoticeForWalletBackup: void => void = () => { + this.isPrivacyNoticeAccepted = !this.isPrivacyNoticeAccepted; }; @action _continueToRecoveryPhraseForWalletBackup: void => void = () => { From 9947f6b4b1b32960d1dad22a2575b3019eb0e512 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 1 Nov 2022 19:04:36 +0300 Subject: [PATCH 177/199] fixing dapp example utxo response handling --- .../yoroi-ergo-connector/example-cardano/index.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/yoroi-ergo-connector/example-cardano/index.js b/packages/yoroi-ergo-connector/example-cardano/index.js index af530b7e76..0f4d074871 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.js +++ b/packages/yoroi-ergo-connector/example-cardano/index.js @@ -373,7 +373,7 @@ window._getUtxos = function (value) { } cardanoApi.getUtxos(value).then(utxosResponse => { toggleSpinner('hide'); - if (utxosResponse.length === 0) { + if (utxosResponse == null || utxosResponse.length === 0) { alertWarrning('NO UTXOS'); } else { utxos = isCBOR() ? mapCborUtxos(utxosResponse) : utxosResponse; @@ -884,12 +884,16 @@ getCollateralUtxos.addEventListener('click', () => { ) .then(utxosResponse => { toggleSpinner('hide'); - let utxos = isCBOR() ? mapCborUtxos(utxosResponse) : utxosResponse; - alertSuccess( - `

    Collateral UTxO (${utxos.length}):

    ` +
    +      if (utxosResponse == null || utxosResponse.length === 0) {
    +        alertWarrning('NO COLLATERAL UTXOS');
    +      } else {
    +        let utxos = isCBOR() ? mapCborUtxos(utxosResponse) : utxosResponse;
    +        alertSuccess(
    +          `

    Collateral UTxO (${utxos.length}):

    ` +
               JSON.stringify(utxos, undefined, 2) +
               '
    ' - ); + ); + } }) .catch(error => { console.error(error); From 94c607ebf6ee6eec4c2e6ed03743c9953f90a563 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 1 Nov 2022 19:09:16 +0300 Subject: [PATCH 178/199] fixing invalid syntax --- packages/yoroi-ergo-connector/example-cardano/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-ergo-connector/example-cardano/index.js b/packages/yoroi-ergo-connector/example-cardano/index.js index 0f4d074871..be8513180a 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.js +++ b/packages/yoroi-ergo-connector/example-cardano/index.js @@ -865,7 +865,7 @@ function createTxHandler(e) { toggleSpinner('hide'); alertWarrning('Creating tx fails'); }); -}); +} getCollateralUtxos.addEventListener('click', () => { toggleSpinner('show'); From 1b4e3270c089a99ff98a7d1babc0f621912f7f7b Mon Sep 17 00:00:00 2001 From: Patriciu Nista Date: Wed, 2 Nov 2022 11:55:00 +0100 Subject: [PATCH 179/199] remove duplicated functions and fix typos --- .../example-cardano/index.js | 746 ++++++++++-------- 1 file changed, 400 insertions(+), 346 deletions(-) diff --git a/packages/yoroi-ergo-connector/example-cardano/index.js b/packages/yoroi-ergo-connector/example-cardano/index.js index af530b7e76..727ce70a52 100644 --- a/packages/yoroi-ergo-connector/example-cardano/index.js +++ b/packages/yoroi-ergo-connector/example-cardano/index.js @@ -1,38 +1,38 @@ -import * as CardanoWasm from '@emurgo/cardano-serialization-lib-browser'; -import { textPartFromWalletChecksumImagePart } from '@emurgo/cip4-js'; -import { createIcon } from '@download/blockies'; -import { getTtl } from './utils'; -import { Bech32Prefix } from '../../yoroi-extension/app/config/stringConfig'; -import { bytesToHex, hexToBytes } from './coreUtils'; - -const cardanoAccessBtnRow = document.querySelector('#request-button-row'); -const cardanoAuthCheck = document.querySelector('#check-identification'); -const cardanoAccessBtn = document.querySelector('#request-access'); -const connectionStatus = document.querySelector('#connection-status'); -const walletPlateSpan = document.querySelector('#wallet-plate'); -const walletIconSpan = document.querySelector('#wallet-icon'); -const getUnUsedAddresses = document.querySelector('#get-unused-addresses'); -const getUsedAddresses = document.querySelector('#get-used-addresses'); -const getChangeAddress = document.querySelector('#get-change-address'); -const getRewardAddresses = document.querySelector('#get-reward-addresses'); -const getAccountBalance = document.querySelector('#get-balance'); -const isEnabledBtn = document.querySelector('#is-enabled'); -const getUtxos = document.querySelector('#get-utxos'); -const submitTx = document.querySelector('#submit-tx'); -const signTx = document.querySelector('#sign-tx'); -const showUtxos = document.querySelector('#show-utxos'); -const createTx = document.querySelector('#create-tx'); -const getCollateralUtxos = document.querySelector('#get-collateral-utxos'); -const signData = document.querySelector('#sign-data'); -const alertEl = document.querySelector('#alert'); -const spinner = document.querySelector('#spinner'); -const utxosContainer = document.querySelector('#utxos'); -const getNFTs = document.getElementById('nfts'); -const getNetworkId = document.getElementById('get-network-id'); +import * as CardanoWasm from "@emurgo/cardano-serialization-lib-browser"; +import { textPartFromWalletChecksumImagePart } from "@emurgo/cip4-js"; +import { createIcon } from "@download/blockies"; +import { getTtl } from "./utils"; +import { Bech32Prefix } from "../../yoroi-extension/app/config/stringConfig"; +import { bytesToHex, hexToBytes } from "./coreUtils"; + +const cardanoAccessBtnRow = document.querySelector("#request-button-row"); +const cardanoAuthCheck = document.querySelector("#check-identification"); +const cardanoAccessBtn = document.querySelector("#request-access"); +const connectionStatus = document.querySelector("#connection-status"); +const walletPlateSpan = document.querySelector("#wallet-plate"); +const walletIconSpan = document.querySelector("#wallet-icon"); +const getUnUsedAddresses = document.querySelector("#get-unused-addresses"); +const getUsedAddresses = document.querySelector("#get-used-addresses"); +const getChangeAddress = document.querySelector("#get-change-address"); +const getRewardAddresses = document.querySelector("#get-reward-addresses"); +const getAccountBalance = document.querySelector("#get-balance"); +const isEnabledBtn = document.querySelector("#is-enabled"); +const getUtxos = document.querySelector("#get-utxos"); +const submitTx = document.querySelector("#submit-tx"); +const signTx = document.querySelector("#sign-tx"); +const showUtxos = document.querySelector("#show-utxos"); +const createTx = document.querySelector("#create-tx"); +const getCollateralUtxos = document.querySelector("#get-collateral-utxos"); +const signData = document.querySelector("#sign-data"); +const alertEl = document.querySelector("#alert"); +const spinner = document.querySelector("#spinner"); +const utxosContainer = document.querySelector("#utxos"); +const getNFTs = document.getElementById("nfts"); +const getNetworkId = document.getElementById("get-network-id"); let accessGranted = false; let cardanoApi; -let returnType = 'cbor'; +let returnType = "cbor"; let utxos; let selectedUtxoIdx = 0; let usedAddresses; @@ -42,16 +42,16 @@ let unsignedTransactionHex; let transactionHex; function isCBOR() { - return returnType === 'cbor'; + return returnType === "cbor"; } const mkcolor = (primary, secondary, spots) => ({ primary, secondary, spots }); const COLORS = [ - mkcolor('#E1F2FF', '#17D1AA', '#A80B32'), - mkcolor('#E1F2FF', '#FA5380', '#0833B2'), - mkcolor('#E1F2FF', '#F06EF5', '#0804F7'), - mkcolor('#E1F2FF', '#EBB687', '#852D62'), - mkcolor('#E1F2FF', '#F59F9A', '#085F48'), + mkcolor("#E1F2FF", "#17D1AA", "#A80B32"), + mkcolor("#E1F2FF", "#FA5380", "#0833B2"), + mkcolor("#E1F2FF", "#F06EF5", "#0804F7"), + mkcolor("#E1F2FF", "#EBB687", "#852D62"), + mkcolor("#E1F2FF", "#F59F9A", "#085F48"), ]; function createBlockiesIcon(seed) { @@ -67,11 +67,11 @@ function createBlockiesIcon(seed) { }); } -toggleSpinner('show'); +toggleSpinner("show"); function onApiConnectied(api) { - toggleSpinner('hide'); - let walletDisplay = 'an anonymous Yoroi Wallet'; + toggleSpinner("hide"); + let walletDisplay = "an anonymous Yoroi Wallet"; api.experimental.setReturnType(returnType); @@ -81,7 +81,10 @@ function onApiConnectied(api) { if (authEnabled) { const walletId = auth.getWalletId(); const pubkey = auth.getWalletPubkey(); - console.log('Auth acquired successfully: ', JSON.stringify({ walletId, pubkey })); + console.log( + "Auth acquired successfully: ", + JSON.stringify({ walletId, pubkey }) + ); const walletPlate = textPartFromWalletChecksumImagePart(walletId); walletDisplay = `Yoroi Wallet ${walletPlate}`; walletIconSpan.appendChild(createBlockiesIcon(walletId)); @@ -89,26 +92,26 @@ function onApiConnectied(api) { alertSuccess(`You have access to ${walletDisplay} now`); walletPlateSpan.innerHTML = walletDisplay; - toggleConnectionUI('status'); + toggleConnectionUI("status"); accessGranted = true; window.cardanoApi = cardanoApi = api; api.experimental.onDisconnect(() => { - alertWarrning(`Disconnected from ${walletDisplay}`); - toggleConnectionUI('button'); - walletPlateSpan.innerHTML = ''; - walletIconSpan.innerHTML = ''; + alertWarning(`Disconnected from ${walletDisplay}`); + toggleConnectionUI("button"); + walletPlateSpan.innerHTML = ""; + walletIconSpan.innerHTML = ""; }); if (authEnabled) { - console.log('Testing auth signatures'); + console.log("Testing auth signatures"); const messageJson = JSON.stringify({ - type: 'this is a random test message object', + type: "this is a random test message object", rndValue: Math.random(), }); const messageHex = bytesToHex(messageJson); console.log( - 'Signing randomized message: ', + "Signing randomized message: ", JSON.stringify({ messageJson, messageHex, @@ -116,22 +119,22 @@ function onApiConnectied(api) { ); const start = performance.now(); auth.signHexPayload(messageHex).then( - sig => { + (sig) => { const elapsed = performance.now() - start; console.log(`Signature created in ${elapsed} ms`); - console.log('Signature received: ', sig); - console.log('Verifying signature against the message'); + console.log("Signature received: ", sig); + console.log("Verifying signature against the message"); auth.checkHexPayload(messageHex, sig).then( - r => { - console.log('Signature matches message: ', r); + (r) => { + console.log("Signature matches message: ", r); }, - e => { - console.error('Sig check failed', e); + (e) => { + console.error("Sig check failed", e); } ); }, - err => { - console.error('Sig failed', err); + (err) => { + console.error("Sig failed", err); } ); } @@ -162,50 +165,50 @@ function reduceWasmMultiasset(multiasset, reducer, initValue) { return result; } -cardanoAccessBtn.addEventListener('click', () => { - toggleSpinner('show'); +cardanoAccessBtn.addEventListener("click", () => { + toggleSpinner("show"); const requestIdentification = cardanoAuthCheck.checked; cardano.yoroi.enable({ requestIdentification }).then( function (api) { onApiConnectied(api); }, function (err) { - toggleSpinner('hide'); + toggleSpinner("hide"); alertError(`Error: ${err}`); } ); }); -isEnabledBtn.addEventListener('click', () => { +isEnabledBtn.addEventListener("click", () => { window.cardano.yoroi.isEnabled().then(function (isEnabled) { alertSuccess(`Is Yoroi connection enabled: ${isEnabled}`); }); }); -getNetworkId.addEventListener('click', () => { +getNetworkId.addEventListener("click", () => { if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); } else { - toggleSpinner('show'); - cardanoApi.getNetworkId().then(networkId => { - console.log('[getNetworkId]', networkId); - toggleSpinner('hide'); + toggleSpinner("show"); + cardanoApi.getNetworkId().then((networkId) => { + console.log("[getNetworkId]", networkId); + toggleSpinner("hide"); }); } }); -getAccountBalance.addEventListener('click', () => { +getAccountBalance.addEventListener("click", () => { if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); } else { - toggleSpinner('show'); - const tokenId = '*'; + toggleSpinner("show"); + const tokenId = "*"; cardanoApi.getBalance(tokenId).then(function (balance) { - console.log('[getBalance]', balance); - toggleSpinner('hide'); + console.log("[getBalance]", balance); + toggleSpinner("hide"); let balanceJson = balance; if (isCBOR()) { - if (tokenId !== '*') { + if (tokenId !== "*") { alertSuccess(`Asset Balance: ${balance} (asset: ${tokenId})`); return; } @@ -216,16 +219,22 @@ getAccountBalance.addEventListener('click', () => { (res, asset) => { res[asset.assetId] = asset.amount; return res; - }, {}); - } - alertSuccess(`Account Balance:
    ${JSON.stringify(balanceJson, null, 2)}
    `) - }); - } + }, + {} + ); + } + alertSuccess( + `Account Balance:
    ${JSON.stringify(balanceJson, null, 2)}
    ` + ); + }); + } }); function addressesFromCborIfNeeded(addresses) { return isCBOR() - ? addresses.map(a => CardanoWasm.Address.from_bytes(hexToBytes(a)).to_bech32()) + ? addresses.map((a) => + CardanoWasm.Address.from_bytes(hexToBytes(a)).to_bech32() + ) : addresses; } @@ -233,85 +242,95 @@ function addressToCbor(address) { return bytesToHex(CardanoWasm.Address.from_bech32(address).to_bytes()); } -getUnUsedAddresses.addEventListener('click', () => { +getUnUsedAddresses.addEventListener("click", () => { if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); } else { - toggleSpinner('show'); + toggleSpinner("show"); cardanoApi.getUnusedAddresses().then(function (addresses) { - toggleSpinner('hide'); + toggleSpinner("hide"); if (addresses.length === 0) { - alertWarrning('No unused addresses'); + alertWarning("No unused addresses"); return; } addresses = addressesFromCborIfNeeded(addresses); unusedAddresses = addresses; alertSuccess(`Address: `); alertEl.innerHTML = - '

    Unused addresses:

    ' + JSON.stringify(addresses, undefined, 2) + '
    '; + "

    Unused addresses:

    " +
    +        JSON.stringify(addresses, undefined, 2) +
    +        "
    "; }); } }); -getUsedAddresses.addEventListener('click', () => { +getUsedAddresses.addEventListener("click", () => { if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); } else { - toggleSpinner('show'); - cardanoApi.getUsedAddresses({ page: 0, limit: 5 }).then(function (addresses) { - toggleSpinner('hide'); - if (addresses.length === 0) { - alertWarrning('No used addresses'); - return; - } - usedAddresses = addressesFromCborIfNeeded(addresses); - alertSuccess(`Address: ${usedAddresses.concat(',')}`); - alertEl.innerHTML = - '

    Used addresses:

    ' + JSON.stringify(usedAddresses, undefined, 2) + '
    '; - }); + toggleSpinner("show"); + cardanoApi + .getUsedAddresses({ page: 0, limit: 5 }) + .then(function (addresses) { + toggleSpinner("hide"); + if (addresses.length === 0) { + alertWarning("No used addresses"); + return; + } + usedAddresses = addressesFromCborIfNeeded(addresses); + alertSuccess(`Address: ${usedAddresses.concat(",")}`); + alertEl.innerHTML = + "

    Used addresses:

    " +
    +          JSON.stringify(usedAddresses, undefined, 2) +
    +          "
    "; + }); } }); -getChangeAddress.addEventListener('click', () => { +getChangeAddress.addEventListener("click", () => { if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); } else { - toggleSpinner('show'); + toggleSpinner("show"); cardanoApi.getChangeAddress().then(function (address) { - toggleSpinner('hide'); + toggleSpinner("hide"); if (address.length === 0) { - alertWarrning('No change addresses'); + alertWarning("No change addresses"); return; } changeAddress = addressesFromCborIfNeeded([address])[0]; alertSuccess(`Address: `); alertEl.innerHTML = - '

    Change address:

    ' + JSON.stringify(address, undefined, 2) + '
    '; + "

    Change address:

    " +
    +        JSON.stringify(address, undefined, 2) +
    +        "
    "; }); } }); -getRewardAddresses.addEventListener('click', () => { +getRewardAddresses.addEventListener("click", () => { if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); } else { - toggleSpinner('show'); + toggleSpinner("show"); cardanoApi.getRewardAddresses().then(function (addresses) { - toggleSpinner('hide'); + toggleSpinner("hide"); if (addresses.length === 0) { - alertWarrning('No change addresses'); + alertWarning("No change addresses"); return; } addresses = addressesFromCborIfNeeded(addresses); - alertSuccess(`Address: ${addresses.concat(',')}`); + alertSuccess(`Address: ${addresses.concat(",")}`); alertEl.innerHTML = - '

    Reward addresses:

    ' + JSON.stringify(addresses, undefined, 2) + '
    '; + "

    Reward addresses:

    " +
    +        JSON.stringify(addresses, undefined, 2) +
    +        "
    "; }); } }); function mapCborUtxos(cborUtxos) { - return cborUtxos.map(hex => { + return cborUtxos.map((hex) => { const u = CardanoWasm.TransactionUnspentOutput.from_bytes(hexToBytes(hex)); const input = u.input(); const output = u.output(); @@ -338,15 +357,17 @@ function mapCborUtxos(cborUtxos) { function valueRequestObjectToWasmHex(requestObj) { const { amount, assets } = requestObj; - const result = CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(String(amount))); + const result = CardanoWasm.Value.new( + CardanoWasm.BigNum.from_str(String(amount)) + ); if (assets != null) { - if (typeof assets !== 'object') { - throw 'Assets is expected to be an object like `{ [policyId]: { [assetName]: amount } }`'; + if (typeof assets !== "object") { + throw "Assets is expected to be an object like `{ [policyId]: { [assetName]: amount } }`"; } const wmasset = CardanoWasm.MultiAsset.new(); for (const [policyId, assets2] of Object.entries(assets)) { - if (typeof assets2 !== 'object') { - throw 'Assets is expected to be an object like `{ [policyId]: { [assetName]: amount } }`'; + if (typeof assets2 !== "object") { + throw "Assets is expected to be an object like `{ [policyId]: { [assetName]: amount } }`"; } const wassets = CardanoWasm.Assets.new(); for (const [assetName, amount] of Object.entries(assets2)) { @@ -355,7 +376,10 @@ function valueRequestObjectToWasmHex(requestObj) { CardanoWasm.BigNum.from_str(String(amount)) ); } - wmasset.insert(CardanoWasm.ScriptHash.from_bytes(hexToBytes(policyId)), wassets); + wmasset.insert( + CardanoWasm.ScriptHash.from_bytes(hexToBytes(policyId)), + wassets + ); } result.set_multiasset(wmasset); } @@ -364,73 +388,75 @@ function valueRequestObjectToWasmHex(requestObj) { window._getUtxos = function (value) { if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); return; } - toggleSpinner('show'); - if (value != null && typeof value !== 'string') { + toggleSpinner("show"); + if (value != null && typeof value !== "string") { value = valueRequestObjectToWasmHex(value); } - cardanoApi.getUtxos(value).then(utxosResponse => { - toggleSpinner('hide'); + cardanoApi.getUtxos(value).then((utxosResponse) => { + toggleSpinner("hide"); if (utxosResponse.length === 0) { - alertWarrning('NO UTXOS'); + alertWarning("NO UTXOS"); } else { utxos = isCBOR() ? mapCborUtxos(utxosResponse) : utxosResponse; alertSuccess( - `

    UTxO (${utxos.length}):

    ` + JSON.stringify(utxos, undefined, 2) + '
    ' + `

    UTxO (${utxos.length}):

    ` +
    +          JSON.stringify(utxos, undefined, 2) +
    +          "
    " ); } }); }; -getUtxos.addEventListener('click', () => { +getUtxos.addEventListener("click", () => { window._getUtxos(); }); -submitTx.addEventListener('click', () => { +submitTx.addEventListener("click", () => { if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); return; } if (!transactionHex) { - alertError('Should sign tx first'); + alertError("Should sign tx first"); return; } - toggleSpinner('show'); + toggleSpinner("show"); cardanoApi .submitTx(transactionHex) - .then(txId => { - toggleSpinner('hide'); + .then((txId) => { + toggleSpinner("hide"); alertSuccess(`Transaction ${txId} submitted`); }) - .catch(error => { - toggleSpinner('hide'); - alertWarrning(`Transaction submission failed: ${JSON.stringify(error)}`); + .catch((error) => { + toggleSpinner("hide"); + alertWarning(`Transaction submission failed: ${JSON.stringify(error)}`); }); }); -const AMOUNT_TO_SEND = '1000000'; +const AMOUNT_TO_SEND = "1000000"; const SEND_TO_ADDRESS = - 'addr_test1qz8xh9w6f2vdnp89xzqlxnusldhz6kdm4rp970gl8swwjjkr3y3kdut55a40jff00qmg74686vz44v6k363md06qkq0q4lztj0'; + "addr_test1qz8xh9w6f2vdnp89xzqlxnusldhz6kdm4rp970gl8swwjjkr3y3kdut55a40jff00qmg74686vz44v6k363md06qkq0q4lztj0"; -signTx.addEventListener('click', () => { - toggleSpinner('show'); +signTx.addEventListener("click", () => { + toggleSpinner("show"); if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); return; } if (!unsignedTransactionHex) { if (!utxos) { - alertError('Should request utxos first'); + alertError("Should request utxos first"); return; } if (!changeAddress) { - alertError('Should request change address first'); + alertError("Should request change address first"); } const txBuilder = CardanoWasm.TransactionBuilder.new( @@ -439,13 +465,13 @@ signTx.addEventListener('click', () => { // linear fee parameters (a*size + b) .fee_algo( CardanoWasm.LinearFee.new( - CardanoWasm.BigNum.from_str('44'), - CardanoWasm.BigNum.from_str('155381') + CardanoWasm.BigNum.from_str("44"), + CardanoWasm.BigNum.from_str("155381") ) ) - .coins_per_utxo_word(CardanoWasm.BigNum.from_str('34482')) - .pool_deposit(CardanoWasm.BigNum.from_str('500000000')) - .key_deposit(CardanoWasm.BigNum.from_str('2000000')) + .coins_per_utxo_word(CardanoWasm.BigNum.from_str("34482")) + .pool_deposit(CardanoWasm.BigNum.from_str("500000000")) + .key_deposit(CardanoWasm.BigNum.from_str("2000000")) .max_value_size(5000) .max_tx_size(16384) .build() @@ -467,7 +493,8 @@ signTx.addEventListener('click', () => { CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(utxo.amount)) ); - const shelleyOutputAddress = CardanoWasm.Address.from_bech32(SEND_TO_ADDRESS); + const shelleyOutputAddress = + CardanoWasm.Address.from_bech32(SEND_TO_ADDRESS); const shelleyChangeAddress = CardanoWasm.Address.from_bech32(changeAddress); // add output to the tx @@ -495,12 +522,14 @@ signTx.addEventListener('click', () => { tx: unsignedTransactionHex, returnTx, }) - .then(responseHex => { - toggleSpinner('hide'); + .then((responseHex) => { + toggleSpinner("hide"); console.log(`[signTx] response: ${responseHex}`); if (returnTx) { - const signedTx = CardanoWasm.Transaction.from_bytes(hexToBytes(responseHex)); + const signedTx = CardanoWasm.Transaction.from_bytes( + hexToBytes(responseHex) + ); const wit = signedTx.witness_set(); const wkeys = wit.vkeys(); @@ -510,173 +539,203 @@ signTx.addEventListener('click', () => { console.log(`[signTx] wit vkey ${i}:`, { vkBytes: bytesToHex(vk.to_bytes()), vkPubBech: vk.public_key().to_bech32(), - vkPubHashBech: vk.public_key().hash().to_bech32(Bech32Prefix.PAYMENT_KEY_HASH), + vkPubHashBech: vk + .public_key() + .hash() + .to_bech32(Bech32Prefix.PAYMENT_KEY_HASH), }); } transactionHex = responseHex; } else { - const witnessSet = CardanoWasm.TransactionWitnessSet.from_bytes(hexToBytes(responseHex)); - const tx = CardanoWasm.Transaction.from_bytes(hexToBytes(unsignedTransactionHex)); - const transaction = CardanoWasm.Transaction.new(tx.body(), witnessSet, tx.auxiliary_data()); + const witnessSet = CardanoWasm.TransactionWitnessSet.from_bytes( + hexToBytes(responseHex) + ); + const tx = CardanoWasm.Transaction.from_bytes( + hexToBytes(unsignedTransactionHex) + ); + const transaction = CardanoWasm.Transaction.new( + tx.body(), + witnessSet, + tx.auxiliary_data() + ); transactionHex = bytesToHex(transaction.to_bytes()); } - unsignedTransactionHex = null; - alertSuccess('Signing tx succeeded: ' + transactionHex) - - }).catch(error => { - console.error(error) - toggleSpinner('hide') - alertWarrning('Signing tx fails') - }) -}) -showUtxos.addEventListener('click', () => { - + unsignedTransactionHex = null; + alertSuccess("Signing tx succeeded: " + transactionHex); + }) + .catch((error) => { + console.error(error); + toggleSpinner("hide"); + alertWarning("Signing tx fails"); + }); +}); +showUtxos.addEventListener("click", () => { if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); return; } if (!utxos || utxos.length === 0) { - alertError('Should request utxos first'); - return + alertError("Should request utxos first"); + return; } - hideAlert() - renderUtxo() -}) + hideAlert(); + renderUtxo(); +}); -function alertError (text) { - toggleSpinner('hide'); - alertEl.className = 'alert alert-danger overflow-scroll' - alertEl.innerHTML = text +function alertError(text) { + toggleSpinner("hide"); + alertEl.className = "alert alert-danger overflow-scroll"; + alertEl.innerHTML = text; } function alertSuccess(text) { - alertEl.className = 'alert alert-success overflow-scroll' - alertEl.innerHTML = text + alertEl.className = "alert alert-success overflow-scroll"; + alertEl.innerHTML = text; } function hideAlert() { - alertEl.className = 'd-none' - alert.innerHTML = '' + alertEl.className = "d-none"; + alert.innerHTML = ""; } -function alertWarrning(text) { - alertEl.className = 'alert alert-warning' - alertEl.innerHTML = text +function alertWarning(text) { + alertEl.className = "alert alert-warning"; + alertEl.innerHTML = text; } -function toggleSpinner(status){ - if(status === 'show') { - spinner.className = 'spinner-border' - alertEl.className = 'd-none' - } else { - spinner.className = 'd-none' - } +function toggleSpinner(status) { + if (status === "show") { + spinner.className = "spinner-border"; + alertEl.className = "d-none"; + } else { + spinner.className = "d-none"; + } } function toggleConnectionUI(status) { - if (status === 'button') { - connectionStatus.classList.add('d-none'); - cardanoAccessBtnRow.classList.remove('d-none'); + if (status === "button") { + connectionStatus.classList.add("d-none"); + cardanoAccessBtnRow.classList.remove("d-none"); } else { - cardanoAccessBtnRow.classList.add('d-none'); - connectionStatus.classList.remove('d-none'); + cardanoAccessBtnRow.classList.add("d-none"); + connectionStatus.classList.remove("d-none"); } } function selectUtxo(e) { - if (!e.target.id) { - alertError("Invalid idx") - return - } - selectedUtxoIdx = e.target.id - hideAlert() - renderUtxo() + if (!e.target.id) { + alertError("Invalid idx"); + return; + } + selectedUtxoIdx = e.target.id; + hideAlert(); + renderUtxo(); } function renderUtxo() { - let utxosHTML = '' - for(let idx in utxos) { - const utxo = utxos[idx] - utxosHTML+= ` -
  • + let utxosHTML = ""; + for (let idx in utxos) { + const utxo = utxos[idx]; + utxosHTML += ` +
  • ${utxo.utxo_id.slice(0, 50)}

    ${utxo.amount}
  • - ` + `; } utxosHTML += ` - ` - utxosContainer.innerHTML = utxosHTML - utxosContainer.classList.remove('d-none') - utxosContainer.classList.add('d-block', 'list-group', 'list-group-numbered', 'mb-5') + `; + utxosContainer.innerHTML = utxosHTML; + utxosContainer.classList.remove("d-none"); + utxosContainer.classList.add( + "d-block", + "list-group", + "list-group-numbered", + "mb-5" + ); // Add select utxo handler for each list item - document.querySelectorAll('.utxo-item').forEach(el => { - el.addEventListener('click', selectUtxo) - }) + document.querySelectorAll(".utxo-item").forEach((el) => { + el.addEventListener("click", selectUtxo); + }); // Add event handler for create tx button - document.querySelector('#create-tx').addEventListener('click', createTxHandler) + document + .querySelector("#create-tx") + .addEventListener("click", createTxHandler); } - function createTxHandler(e) { - toggleSpinner('show'); + toggleSpinner("show"); if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); return; } if (!utxos || utxos.length === 0) { - alertError('Should request utxos first'); + alertError("Should request utxos first"); return; } if (!usedAddresses || usedAddresses.length === 0) { - alertError('Should request used addresses first'); + alertError("Should request used addresses first"); return; } const selectedUtxo = utxos[selectedUtxoIdx]; if (!selectedUtxo) { - alertError('Failed to select a random utxo from the available list!'); + alertError("Failed to select a random utxo from the available list!"); return; } - console.log('[createTx] Including random utxo input: ', selectedUtxo); + console.log("[createTx] Including random utxo input: ", selectedUtxo); const usedAddress = usedAddresses[0]; - const keyHash = CardanoWasm.BaseAddress.from_address(CardanoWasm.Address.from_bech32(usedAddress)) + const keyHash = CardanoWasm.BaseAddress.from_address( + CardanoWasm.Address.from_bech32(usedAddress) + ) .payment_cred() .to_keyhash(); const keyHashBech = keyHash.to_bech32(Bech32Prefix.PAYMENT_KEY_HASH); const scripts = CardanoWasm.NativeScripts.new(); - scripts.add(CardanoWasm.NativeScript.new_script_pubkey(CardanoWasm.ScriptPubkey.new(keyHash))); - scripts.add(CardanoWasm.NativeScript.new_timelock_start(CardanoWasm.TimelockStart.new(42))); + scripts.add( + CardanoWasm.NativeScript.new_script_pubkey( + CardanoWasm.ScriptPubkey.new(keyHash) + ) + ); + scripts.add( + CardanoWasm.NativeScript.new_timelock_start( + CardanoWasm.TimelockStart.new(42) + ) + ); - const mintScript = CardanoWasm.NativeScript.new_script_all(CardanoWasm.ScriptAll.new(scripts)); + const mintScript = CardanoWasm.NativeScript.new_script_all( + CardanoWasm.ScriptAll.new(scripts) + ); const mintScriptHex = bytesToHex(mintScript.to_bytes()); function convertAssetNameToHEX(name) { return bytesToHex(name); } - const tokenAssetName = 'V42'; + const tokenAssetName = "V42"; const nftAssetName = `V42/NFT#${Math.floor(Math.random() * 1000000000)}`; const tokenAssetNameHex = convertAssetNameToHEX(tokenAssetName); const nftAssetNameHex = convertAssetNameToHEX(nftAssetName); const expectedPolicyId = bytesToHex(mintScript.hash().to_bytes()); - console.log('[createTx] Including mint request: ', { + console.log("[createTx] Including mint request: ", { keyHashBech, mintScriptHex, assetNameHex: tokenAssetNameHex, @@ -686,7 +745,7 @@ function createTxHandler(e) { const outputHex = bytesToHex( CardanoWasm.TransactionOutput.new( CardanoWasm.Address.from_bech32(selectedUtxo.receiver), - CardanoWasm.Value.new(CardanoWasm.BigNum.from_str('1000000')), + CardanoWasm.Value.new(CardanoWasm.BigNum.from_str("1000000")) ).to_bytes() ); @@ -720,14 +779,24 @@ function createTxHandler(e) { // noinspection PointlessBooleanExpressionJS if (nativeScriptInputUtxoId != null) { const nscripts = CardanoWasm.NativeScripts.new(); - nscripts.add(CardanoWasm.NativeScript.new_timelock_start(CardanoWasm.TimelockStart.new(1234))); - nscripts.add(CardanoWasm.NativeScript.new_timelock_start(CardanoWasm.TimelockStart.new(1))); + nscripts.add( + CardanoWasm.NativeScript.new_timelock_start( + CardanoWasm.TimelockStart.new(1234) + ) + ); + nscripts.add( + CardanoWasm.NativeScript.new_timelock_start( + CardanoWasm.TimelockStart.new(1) + ) + ); const nativeScript = CardanoWasm.NativeScript.new_script_all( CardanoWasm.ScriptAll.new(nscripts) ); const scriptHash = nativeScript.hash(); - console.log(`[createTx] Native script hash: ${bytesToHex(scriptHash.to_bytes())}`); + console.log( + `[createTx] Native script hash: ${bytesToHex(scriptHash.to_bytes())}` + ); const nativeScriptAddress = CardanoWasm.EnterpriseAddress.new( 0, CardanoWasm.StakeCredential.from_scripthash(scriptHash) @@ -747,11 +816,15 @@ function createTxHandler(e) { // noinspection PointlessBooleanExpressionJS if (plutusScriptInputUtxoId != null || createPlutusTarget) { const plutusScript = CardanoWasm.PlutusScript.from_bytes( - hexToBytes('4e4d01000033222220051200120011') + hexToBytes("4e4d01000033222220051200120011") ); const plutusScriptHash = plutusScript.hash(); - console.log(`[createTx] Plutus script hash: ${bytesToHex(plutusScriptHash.to_bytes())}`); + console.log( + `[createTx] Plutus script hash: ${bytesToHex( + plutusScriptHash.to_bytes() + )}` + ); const plutusScriptAddress = CardanoWasm.EnterpriseAddress.new( 0, CardanoWasm.StakeCredential.from_scripthash(plutusScriptHash) @@ -760,8 +833,12 @@ function createTxHandler(e) { .to_bech32(); console.log(`[createTx] Plutus script address: ${plutusScriptAddress}`); - const datum = CardanoWasm.PlutusData.new_empty_constr_plutus_data(CardanoWasm.BigNum.zero()); - const datumHash = bytesToHex(CardanoWasm.hash_plutus_data(datum).to_bytes()); + const datum = CardanoWasm.PlutusData.new_empty_constr_plutus_data( + CardanoWasm.BigNum.zero() + ); + const datumHash = bytesToHex( + CardanoWasm.hash_plutus_data(datum).to_bytes() + ); console.log(`[createTx] Plutus datum hash: ${datumHash}`); if (createPlutusTarget) { @@ -774,10 +851,12 @@ function createTxHandler(e) { const redeemer = CardanoWasm.Redeemer.new( CardanoWasm.RedeemerTag.new_spend(), CardanoWasm.BigNum.zero(), - CardanoWasm.PlutusData.new_empty_constr_plutus_data(CardanoWasm.BigNum.zero()), + CardanoWasm.PlutusData.new_empty_constr_plutus_data( + CardanoWasm.BigNum.zero() + ), CardanoWasm.ExUnits.new( - CardanoWasm.BigNum.from_str('1700'), - CardanoWasm.BigNum.from_str('476468') + CardanoWasm.BigNum.from_str("1700"), + CardanoWasm.BigNum.from_str("476468") ) ); @@ -795,13 +874,13 @@ function createTxHandler(e) { if (includeDefaultTargets) { includeTargets.push({ address: targetAddress, - value: '2000000', + value: "2000000", dataHash: targetDataHash, mintRequest: [ { script: mintScriptHex, assetName: tokenAssetNameHex, - amount: '42', + amount: "42", }, { script: mintScriptHex, @@ -812,13 +891,13 @@ function createTxHandler(e) { json: JSON.stringify({ name: nftAssetName, description: `V42 NFT Collection`, - mediaType: 'image/png', - image: 'ipfs://QmRhTTbUrPYEw3mJGGhQqQST9k86v1DPBiTTWJGKDJsVFw', + mediaType: "image/png", + image: "ipfs://QmRhTTbUrPYEw3mJGGhQqQST9k86v1DPBiTTWJGKDJsVFw", files: [ { name: nftAssetName, - mediaType: 'image/png', - src: 'ipfs://QmRhTTbUrPYEw3mJGGhQqQST9k86v1DPBiTTWJGKDJsVFw', + mediaType: "image/png", + src: "ipfs://QmRhTTbUrPYEw3mJGGhQqQST9k86v1DPBiTTWJGKDJsVFw", }, ], }), @@ -836,17 +915,18 @@ function createTxHandler(e) { }; if (includeAssetTargets) { - const utxosWithAssets = utxos.filter(u => u.assets.length > 0); - const utxoWithAssets = utxosWithAssets[Math.floor(Math.random() * utxosWithAssets.length)]; + const utxosWithAssets = utxos.filter((u) => u.assets.length > 0); + const utxoWithAssets = + utxosWithAssets[Math.floor(Math.random() * utxosWithAssets.length)]; if (utxoWithAssets) { const asset = utxoWithAssets.assets[0]; - console.log('[createTx] Including asset:', asset); + console.log("[createTx] Including asset:", asset); txReq.includeTargets.push({ // do not specify value, the connector will use minimum value address: selectedUtxo.receiver, assets: { - [asset.assetId]: '1', + [asset.assetId]: "1", }, ensureRequiredMinimalValue: true, }); @@ -855,54 +935,56 @@ function createTxHandler(e) { cardanoApi.experimental .createTx(txReq, true) - .then(txHex => { - toggleSpinner('hide'); + .then((txHex) => { + toggleSpinner("hide"); alertSuccess(`

    Creating tx succeeds: ${txHex}

    `); unsignedTransactionHex = txHex; }) - .catch(error => { + .catch((error) => { console.error(error); - toggleSpinner('hide'); - alertWarrning('Creating tx fails'); + toggleSpinner("hide"); + alertWarning("Creating tx fails"); }); -}); +} -getCollateralUtxos.addEventListener('click', () => { - toggleSpinner('show'); +getCollateralUtxos.addEventListener("click", () => { + toggleSpinner("show"); if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); return; } - const amount = '4900000'; + const amount = "4900000"; cardanoApi .getCollateralUtxos( - Buffer.from(CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(amount)).to_bytes()).toString( - 'hex' - ) + Buffer.from( + CardanoWasm.Value.new(CardanoWasm.BigNum.from_str(amount)).to_bytes() + ).toString("hex") ) - .then(utxosResponse => { - toggleSpinner('hide'); + .then((utxosResponse) => { + toggleSpinner("hide"); let utxos = isCBOR() ? mapCborUtxos(utxosResponse) : utxosResponse; alertSuccess( `

    Collateral UTxO (${utxos.length}):

    ` +
               JSON.stringify(utxos, undefined, 2) +
    -          '
    ' + "
    " ); }) - .catch(error => { + .catch((error) => { console.error(error); - toggleSpinner('hide'); - alertWarrning(`Getting collateral UTXOs tx fails: ${JSON.stringify(error)}`); + toggleSpinner("hide"); + alertWarning( + `Getting collateral UTXOs tx fails: ${JSON.stringify(error)}` + ); }); }); -signData.addEventListener('click', () => { - toggleSpinner('show'); +signData.addEventListener("click", () => { + toggleSpinner("show"); if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); return; } @@ -912,7 +994,7 @@ signData.addEventListener('click', () => { } else if (unusedAddresses && unusedAddresses.length > 0) { address = unusedAddresses[0]; } else { - alertError('Should request used or unused addresses first'); + alertError("Should request used or unused addresses first"); return; } @@ -920,34 +1002,34 @@ signData.addEventListener('click', () => { address = addressToCbor(address); } - const payload = document.querySelector('#sign-data-payload').value; + const payload = document.querySelector("#sign-data-payload").value; let payloadHex; - if (payload.startsWith('0x')) { - payloadHex = Buffer.from(payload.replace('^0x', ''), 'hex').toString('hex'); + if (payload.startsWith("0x")) { + payloadHex = Buffer.from(payload.replace("^0x", ""), "hex").toString("hex"); } else { - payloadHex = Buffer.from(payload, 'utf8').toString('hex'); + payloadHex = Buffer.from(payload, "utf8").toString("hex"); } - console.log('[signData][address] ', address); + console.log("[signData][address] ", address); cardanoApi .signData(address, payloadHex) - .then(sig => { - alertSuccess('Signature:' + JSON.stringify(sig)); + .then((sig) => { + alertSuccess("Signature:" + JSON.stringify(sig)); }) - .catch(error => { + .catch((error) => { console.error(error); alertError(error.info); }) .then(() => { - toggleSpinner('hide'); + toggleSpinner("hide"); }); }); -getNFTs.addEventListener('click', async () => { - toggleSpinner('show'); +getNFTs.addEventListener("click", async () => { + toggleSpinner("show"); if (!accessGranted) { - alertError('Should request access first'); + alertError("Should request access first"); return; } @@ -958,69 +1040,41 @@ getNFTs.addEventListener('click', async () => { console.error(error); alertError(error.message); } - toggleSpinner('hide'); -}) - -function alertError(text) { - toggleSpinner('hide'); - alertEl.className = 'alert alert-danger'; - alertEl.innerHTML = text; -} - -function alertSuccess(text) { - alertEl.className = 'alert alert-success'; - alertEl.innerHTML = text; -} - -function alertWarrning(text) { - alertEl.className = 'alert alert-warning'; - alertEl.innerHTML = text; -} + toggleSpinner("hide"); +}); -function renderJonsResponse (title, response) { +function renderJonsResponse(title, response) { alertSuccess( - `

    ${title}:

    ` + JSON.stringify(response, undefined, 2) + '
    ' - ) -} - -function toggleSpinner(status) { - if (status === 'show') { - spinner.className = 'spinner-border'; - alertEl.className = 'd-none'; - } else { - spinner.className = 'd-none'; - } -} - -function toggleConnectionUI(status) { - if (status === 'button') { - connectionStatus.classList.add('d-none'); - cardanoAccessBtnRow.classList.remove('d-none'); - } else { - cardanoAccessBtnRow.classList.add('d-none'); - connectionStatus.classList.remove('d-none'); - } + `

    ${title}:

    ` +
    +      JSON.stringify(response, undefined, 2) +
    +      "
    " + ); } window.onload = () => { - if (typeof window.cardano === 'undefined') { - alertError('Cardano API not found'); + if (typeof window.cardano === "undefined") { + alertError("Cardano API not found"); } else { - console.log('Cardano API detected, checking connection status'); - cardano.yoroi.enable({ requestIdentification: true, onlySilent: true }).then( - api => { - console.log('successful silent reconnection'); - onApiConnectied(api); - }, - err => { - if (String(err).includes('onlySilent:fail')) { - console.log('no silent re-connection available'); - } else { - console.error('Silent reconnection failed for unknown reason!', err); + console.log("Cardano API detected, checking connection status"); + cardano.yoroi + .enable({ requestIdentification: true, onlySilent: true }) + .then( + (api) => { + console.log("successful silent reconnection"); + onApiConnectied(api); + }, + (err) => { + if (String(err).includes("onlySilent:fail")) { + console.log("no silent re-connection available"); + } else { + console.error( + "Silent reconnection failed for unknown reason!", + err + ); + } + toggleSpinner("hide"); + toggleConnectionUI("button"); } - toggleSpinner('hide'); - toggleConnectionUI('button'); - } - ); + ); } }; From cacedd801498f7d83ff44793eeeb2c3fbff4c241 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 3 Nov 2022 15:45:42 +0300 Subject: [PATCH 180/199] test doc is updated --- packages/yoroi-extension/docs/TEST.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/yoroi-extension/docs/TEST.md b/packages/yoroi-extension/docs/TEST.md index c90695e877..4aceab2106 100644 --- a/packages/yoroi-extension/docs/TEST.md +++ b/packages/yoroi-extension/docs/TEST.md @@ -25,6 +25,8 @@ You **must** run `npm run test:build` **before** running the tests! Rerun `test:build` anytime you make changes to the application itself. If you only change test files, you do not need to rerun it. +You **must** create the folder `reports` inside the folder `yoroi-extension` before running tests. + ```bash # features (command to run all existing tests) npm run test:run:e2e:chrome @@ -74,9 +76,13 @@ After executing the above commands to run tests, cucumber-html-reporter can be u ```bash # How to create html reports after a test run -node reportGenerator.js +npm run create-report ``` +The html report file will be automatically opened in your browser. + +The report file `cucumberReport.html` will be located in the `reports` folder. + ### Storybook You can easily inspect the whole UI by running Storybook. It is useful for developing also because it supports hot-reload. From 0ccf672759bcbd077d2da480734969cb707350c2 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 3 Nov 2022 17:30:13 +0300 Subject: [PATCH 181/199] Updated README.md in the yoroi-ergo-connector --- packages/yoroi-ergo-connector/README.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/yoroi-ergo-connector/README.md b/packages/yoroi-ergo-connector/README.md index d7eca7adac..52e0dd63c4 100644 --- a/packages/yoroi-ergo-connector/README.md +++ b/packages/yoroi-ergo-connector/README.md @@ -1,15 +1,19 @@ # Yoroi dApp Connector -This experimental extension is the first in a modular design to interface the [Yoroi frontend extension](https://github.com/Emurgo/yoroi-frontend) with dApps. It targets the [Ergo](https://ergoplatform.org/en/) cryptocurrency. +~~This experimental extension is the first in a modular design to interface the [Yoroi frontend extension](https://github.com/Emurgo/yoroi-frontend) with dApps. It targets the [Ergo](https://ergoplatform.org/en/) cryptocurrency.~~ -The implementation will follow our [EIP-0012](https://github.com/ergoplatform/eips/pull/23) spec. +~~The implementation will follow our [EIP-0012](https://github.com/ergoplatform/eips/pull/23) spec.~~ + +This project is used only as an example application. + +All related to the dApp is moved to the folder `packages/yoroi-extension/app/ergo-connector`. ### Testing -1. Run the Yoroi Extension and get its extension ID -1. Use `npm run prod:custom -- --yoroiExtensionId=extension-id-here` -1. Select "load unpacked" in your browser and select the build folder (or ZIP file also works in prod/nightly builds) -1. Note the extension ID of the connector extension. Now, when you re-build Yoroi passing `--ergoConnectorExtensionId=connector-extension-id-here` as a build argument. -Example: `npm run dev:stable -- --ergoConnectorExtensionId=ebnncddeiookdmpglbhiamljhpdgbjcm` -1. Build the example project in the `example` folder (`npm install && npm run start`) -1. Open the page from the example project +1. Build the test version of the extension. (Read how to build the test app [here](../yoroi-extension/docs/TEST.md#e2e-tests)) +2. Use `npm run test:run:e2e:dApp:chrome` to run all dApp-connector related tests + +### Running dApp example page + +1. Install node modules +2. Run example for cardano network `npm run cardano` or run example for ergo network `npm run ergo` From 9f70258b033bb277819f1b0551cb41c0137f7ae7 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 3 Nov 2022 18:21:09 +0300 Subject: [PATCH 182/199] flow fixes --- .../ergo-connector/components/signin/CardanoSignTxPage.js | 5 +++++ .../app/ergo-connector/components/signin/SignTxPage.js | 5 +++++ .../ergo-connector/containers/ConnectContainer.stories.js | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js index 14c5bbc3de..e2b252a60f 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/CardanoSignTxPage.js @@ -495,6 +495,11 @@ class SignTxPage extends Component { shouldHideBalance={this.props.shouldHideBalance} publicDeriver={this.props.selectedWallet} getTokenInfo={this.props.getTokenInfo} + getCurrentPrice={(_from, _to) => null} + unitOfAccountSetting={({ + enabled: false, + currency: null, + })} /> diff --git a/packages/yoroi-extension/app/ergo-connector/components/signin/SignTxPage.js b/packages/yoroi-extension/app/ergo-connector/components/signin/SignTxPage.js index c224180f45..f3a4f5e9e6 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/signin/SignTxPage.js +++ b/packages/yoroi-extension/app/ergo-connector/components/signin/SignTxPage.js @@ -319,6 +319,11 @@ class SignTxPage extends Component { shouldHideBalance={this.props.shouldHideBalance} publicDeriver={this.props.selectedWallet} getTokenInfo={this.props.getTokenInfo} + getCurrentPrice={(_from, _to) => null} + unitOfAccountSetting={({ + enabled: false, + currency: null, + })} /> diff --git a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.stories.js b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.stories.js index 12da2ab4c1..3c92c72120 100644 --- a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.stories.js +++ b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.stories.js @@ -65,6 +65,10 @@ export const Generic = (): Node => { stores: { profile: { shouldHideBalance: false, + unitOfAccount: { + enabled: false, + currency: null, + } }, connector: { connectingMessage: { @@ -82,6 +86,9 @@ export const Generic = (): Node => { tokenInfoStore: { tokenInfo: mockFromDefaults(defaultAssets), }, + coinPriceStore: { + getCurrentPrice: (_from, _to) => null, + }, }, actions: { connector: { From ab829758dadfa0ec6cca3f5be674569a9c676557 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 10 Nov 2022 12:50:41 +0200 Subject: [PATCH 183/199] Rebuild the transaction if receiver address changes --- .../app/stores/toplevel/TransactionBuilderStore.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/yoroi-extension/app/stores/toplevel/TransactionBuilderStore.js b/packages/yoroi-extension/app/stores/toplevel/TransactionBuilderStore.js index 0ae6ea2695..6bd2903226 100644 --- a/packages/yoroi-extension/app/stores/toplevel/TransactionBuilderStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/TransactionBuilderStore.js @@ -208,6 +208,7 @@ export default class TransactionBuilderStore extends Store { this._updateTxBuilder() From 282921ca2449a463625cb7bf4d8ab0104c860a40 Mon Sep 17 00:00:00 2001 From: yushi Date: Mon, 31 Oct 2022 21:05:10 +0800 Subject: [PATCH 184/199] bump yoroi-lib version --- .../features/mock-chain/mockCardanoServer.js | 24 ++++++++++++++----- packages/yoroi-extension/package.json | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js index da294f92af..161fa6b502 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js @@ -368,14 +368,22 @@ export function getMockServer(settings: { }); server.post('/api/v2/txs/utxoDiffSincePoint', async (req, res) => { - // ignore `blockCount` and returns all diff items at once - const { addresses, untilBlockHash, afterBlockHash, /* blockCount */ } = req.body; + const { addresses, untilBlockHash, afterPoint, diffLimit } = req.body; + if (afterPoint.lastPage) { + res.send({ + diffItems: [], + lastDiffPointSelected: { + lastPage: true, + }, + }); + return; + } + const { result, value } = await mockImporter.mockUtxoApi.getUtxoDiffSincePoint( { addresses, untilBlockHash, - // ignore itemIndex and txHash - afterBestBlock: afterBlockHash + afterBestBlock: afterPoint.blockHash }, ); if (result !== 'SUCCESS') { @@ -402,9 +410,13 @@ export function getMockServer(settings: { }; }); res.send({ - diffItems, // no pagination, always return all at once - lastBlockHash: untilBlockHash, + diffItems, + // note this isn't exactly what the real server would return but + // this value is opaque to the client so it shouldn't break anything + lastDiffPointSelected: { + lastPage: true, + }, }); } }); diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index 5539691590..38a6883876 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -185,7 +185,7 @@ "@emurgo/cip14-js": "2.0.0", "@emurgo/cip4-js": "1.0.5", "@emurgo/js-chain-libs": "0.7.1", - "@emurgo/yoroi-lib": "0.1.3", + "@emurgo/yoroi-lib": "0.1.5", "@mui/lab": "^5.0.0-alpha.51", "@mui/material": "^5.0.4", "@svgr/webpack": "5.5.0", From 2aff05c6a1efce300f2dea5be78fd89dc2cb0a1d Mon Sep 17 00:00:00 2001 From: Philipp Henkel Date: Sun, 27 Nov 2022 12:57:29 +0100 Subject: [PATCH 185/199] Support array-based IPFS image URLs for NFTs --- packages/yoroi-extension/app/utils/nftMetadata.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/utils/nftMetadata.js b/packages/yoroi-extension/app/utils/nftMetadata.js index 0e7f916020..986e3c618f 100644 --- a/packages/yoroi-extension/app/utils/nftMetadata.js +++ b/packages/yoroi-extension/app/utils/nftMetadata.js @@ -100,8 +100,11 @@ export function getImageFromTokenMetadata( if (typeof nftMetadata.image === 'string') { return nftMetadata.image; } - if (typeof nftMetadata.image?.[0] === 'string') { - return nftMetadata.image[0]; + if ( + isArray(nftMetadata.image) && + nftMetadata.image.every(s => typeof s === 'string') + ) { + return nftMetadata.image.join(''); } return null; } From 8a03b118a2ef6680143fd70452dd9a2871bf06b3 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Fri, 2 Dec 2022 01:17:57 +0300 Subject: [PATCH 186/199] yoroi-lib version bump --- packages/yoroi-extension/package-lock.json | 489 ++++++++++++++++----- packages/yoroi-extension/package.json | 2 +- 2 files changed, 375 insertions(+), 116 deletions(-) diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index 648bd0bc64..e1276f22d4 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -1702,6 +1702,54 @@ "fnv-plus": "1.3.1" } }, + "@emurgo/cross-csl-core": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emurgo/cross-csl-core/-/cross-csl-core-1.0.4.tgz", + "integrity": "sha512-bSvhUJBO1N/6bE6I/dTz9xiK8T/m5+0W0NOdheXRVWcWqfBPwo0xxO8/aa86tT/p3zQy9Gvc9FgWmt96ZcMSug==", + "requires": { + "@cardano-foundation/ledgerjs-hw-app-cardano": "^5.0.0", + "@types/mocha": "^9.1.1", + "axios": "^0.24.0", + "bech32": "^2.0.0", + "bignumber.js": "^9.0.1", + "blake2b": "^2.1.4", + "hash-wasm": "^4.9.0", + "mocha": "^10.0.0" + }, + "dependencies": { + "axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "requires": { + "follow-redirects": "^1.14.4" + } + }, + "blake2b": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/blake2b/-/blake2b-2.1.4.tgz", + "integrity": "sha512-AyBuuJNI64gIvwx13qiICz6H6hpmjvYS5DGkG6jbXMOT8Z3WUJ3V1X0FlhIoT1b/5JtHE3ki+xjtMvu1nn+t9A==", + "requires": { + "blake2b-wasm": "^2.4.0", + "nanoassert": "^2.0.0" + } + }, + "blake2b-wasm": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/blake2b-wasm/-/blake2b-wasm-2.4.0.tgz", + "integrity": "sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w==", + "requires": { + "b4a": "^1.0.1", + "nanoassert": "^2.0.0" + } + }, + "nanoassert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", + "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==" + } + } + }, "@emurgo/js-chain-libs": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/@emurgo/js-chain-libs/-/js-chain-libs-0.7.1.tgz", @@ -1713,12 +1761,13 @@ "integrity": "sha512-j48d8WqwHAAwMRdHyaP/nIhkT7OCNvwFH7WJKi6YPncS0NsVhLBM+CXRIvNd2mNEN8TviVsqJrwkqWIuc0Bj/g==", "dev": true }, - "@emurgo/yoroi-lib-core": { - "version": "0.9.3-alpha.55", - "resolved": "https://registry.npmjs.org/@emurgo/yoroi-lib-core/-/yoroi-lib-core-0.9.3-alpha.55.tgz", - "integrity": "sha512-AUfpNtynaAh6SsVVtBfcH+TZ7qvJnGNepL8r+nXLUBg2VRvrc5RA6dJ5UY+NE9T5Sr9nypgkgJ6Y+hKK3dddlA==", + "@emurgo/yoroi-lib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@emurgo/yoroi-lib/-/yoroi-lib-0.2.0.tgz", + "integrity": "sha512-lcYFco0x7gxIrPXWxxw3Fz1AtWqY9do3l7cFZINH29FUSfu3yUR/pvU/TMlSZBROs3yXv7BsMuGWYVMdUoZ98w==", "requires": { - "@cardano-foundation/ledgerjs-hw-app-cardano": "^5.0.0", + "@cardano-foundation/ledgerjs-hw-app-cardano": "^5.1.0", + "@emurgo/cross-csl-core": "^1.0.0", "axios": "^0.24.0", "bech32": "^2.0.0", "bignumber.js": "^9.0.1", @@ -7794,6 +7843,11 @@ "@types/node": "*" } }, + "@types/mocha": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==" + }, "@types/node": { "version": "16.11.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", @@ -8552,7 +8606,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -9028,9 +9081,9 @@ "dev": true }, "b4a": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.0.tgz", - "integrity": "sha512-fsTxXxj1081Yq5MOQ06gZ5+e2QcSyP2U6NofdOWyq+lrNI4IjkZ+fLVmoQ6uUCiNg1NWePMMVq93vOTdbJmErw==" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.1.tgz", + "integrity": "sha512-AsKjNhz72yxteo/0EtQEiwkMUgk/tGmycXlbG4g3Ard2/ULtNLUykGOkeK0egmN27h0xMAhb76jYccW+XTBExA==" }, "babel-eslint": { "version": "11.0.0-beta.2", @@ -9755,8 +9808,7 @@ "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "bindings": { "version": "1.5.0", @@ -10019,7 +10071,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10028,8 +10079,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" } } }, @@ -10037,7 +10087,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -10053,6 +10102,11 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -10682,14 +10736,14 @@ "dev": true }, "chromedriver": { - "version": "105.0.1", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-105.0.1.tgz", - "integrity": "sha512-QqylH9mvl4Ybq3mmHsym7jeq/LhEi2sPtD8ffd9ixiDFdPRlh2F4vzrzK+myj1MiXb0TYJK7+OCcMEmsB3Sm/Q==", + "version": "107.0.3", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-107.0.3.tgz", + "integrity": "sha512-jmzpZgctCRnhYAn0l/NIjP4vYN3L8GFVbterTrRr2Ly3W5rFMb9H8EKGuM5JCViPKSit8FbE718kZTEt3Yvffg==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.3", - "axios": "^0.27.2", - "del": "^6.1.1", + "axios": "^1.1.3", + "compare-versions": "^5.0.1", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^1.1.0", @@ -10697,13 +10751,14 @@ }, "dependencies": { "axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.0.tgz", + "integrity": "sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw==", "dev": true, "requires": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "follow-redirects": { @@ -10868,7 +10923,6 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -10878,20 +10932,17 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10902,7 +10953,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -11031,6 +11081,12 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "compare-versions": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz", + "integrity": "sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -11148,8 +11204,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -12735,22 +12790,6 @@ } } }, - "del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", - "dev": true, - "requires": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -13244,8 +13283,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "emojis-list": { "version": "3.0.0", @@ -14865,7 +14903,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -14954,6 +14991,11 @@ "locate-path": "^3.0.0" } }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -15723,14 +15765,12 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "optional": true }, "fstream": { @@ -15946,8 +15986,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-func-name": { "version": "2.0.0", @@ -16019,7 +16058,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -16069,7 +16107,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -16469,8 +16506,7 @@ "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, "highlight.js": { "version": "10.7.3", @@ -16915,7 +16951,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -17072,7 +17107,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -17196,8 +17230,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-finite": { "version": "1.1.0", @@ -17236,7 +17269,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -17276,8 +17308,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-number-object": { "version": "1.0.6", @@ -17299,12 +17330,6 @@ "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", "dev": true }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -17410,6 +17435,11 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" + }, "is-url": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", @@ -19957,6 +19987,60 @@ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", "dev": true }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -20609,7 +20693,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -20748,6 +20831,184 @@ "resolved": "https://registry.npmjs.org/mobx-react-router/-/mobx-react-router-4.1.0.tgz", "integrity": "sha512-2knsbDqVorWLngZWbdO8tr7xcZXaLpVFsFlCaGaoyZ+EP9erVGRxnlWGqKyFObs3EH1JPLyTDOJ2LPTxb/lB6Q==" }, + "mocha": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", + "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==" + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + } + } + }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -21297,8 +21558,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-range": { "version": "0.1.2", @@ -21552,7 +21812,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -22118,8 +22377,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", @@ -22190,8 +22448,7 @@ "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" }, "pify": { "version": "4.0.1", @@ -23936,7 +24193,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -24342,8 +24598,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-from-string": { "version": "2.0.2", @@ -26056,8 +26311,7 @@ "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, "style-loader": { "version": "2.0.0", @@ -26683,7 +26937,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -28591,11 +28844,15 @@ "microevent.ts": "~0.1.1" } }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==" + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -28605,14 +28862,12 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -28621,7 +28876,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -28629,20 +28883,17 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -28653,7 +28904,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -28663,8 +28913,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "3.0.3", @@ -28731,7 +28980,6 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -28745,20 +28993,17 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -28769,7 +29014,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -28777,16 +29021,32 @@ "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" } } }, "yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" + } + } }, "yauzl": { "version": "2.10.0", @@ -28801,8 +29061,7 @@ "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, "zip-stream": { "version": "2.1.3", diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index 38a6883876..da9457156c 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -185,7 +185,7 @@ "@emurgo/cip14-js": "2.0.0", "@emurgo/cip4-js": "1.0.5", "@emurgo/js-chain-libs": "0.7.1", - "@emurgo/yoroi-lib": "0.1.5", + "@emurgo/yoroi-lib": "0.2.0", "@mui/lab": "^5.0.0-alpha.51", "@mui/material": "^5.0.4", "@svgr/webpack": "5.5.0", From e14455b8d4c9c98e1aa9524eff6d1e1b52707f24 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Fri, 2 Dec 2022 01:21:29 +0300 Subject: [PATCH 187/199] version bump: 4.17.001 --- packages/yoroi-extension/package-lock.json | 2 +- packages/yoroi-extension/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index e1276f22d4..674abed53c 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.17.0", + "version": "4.17.001", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index da9457156c..af710bd6f1 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.17.0", + "version": "4.17.001", "description": "Cardano ADA wallet", "scripts": { "dev:build": "rimraf dev/ && babel-node scripts/build --type=debug", From 979fea2aba069c2bda0b6031012e63955c25a78d Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Fri, 2 Dec 2022 15:53:02 +0300 Subject: [PATCH 188/199] flow fixes --- .../api/ada/lib/state-fetch/mockNetwork.js | 20 +++++++++++++++---- .../features/mock-chain/mockCardanoServer.js | 4 ++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js index 460c2fb06d..d174b8d65d 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js @@ -860,7 +860,7 @@ export class MockUtxoApi implements UtxoApiContract { } async getUtxoDiffSincePoint(req: UtxoDiffSincePointRequest): Promise> { - const { addresses, untilBlockHash, afterBestBlock, } = req; + const { addresses, untilBlockHash, afterBestBlocks, } = req; const hexAddresses = addresses.map(a => { const hex = fixAddresses(a, networks.CardanoMainnet); @@ -872,6 +872,7 @@ export class MockUtxoApi implements UtxoApiContract { let seenUntilBlock = false; const utxoDiffItems = []; + let lastFoundBestBlock = null; for (let i = this.blockchain.length - 1; i >= 0; i--) { const tx = this.blockchain[i]; if (tx.tx_state !== 'Successful') { @@ -882,8 +883,13 @@ export class MockUtxoApi implements UtxoApiContract { seenUntilBlock = true; } + const txInAfterBestblocks = afterBestBlocks.includes(tx.block_hash); + if (txInAfterBestblocks && lastFoundBestBlock == null) { + lastFoundBestBlock = tx.block_hash; + } + if (seenUntilBlock) { - if (tx.block_hash === afterBestBlock) { + if (txInAfterBestblocks) { break; } @@ -931,14 +937,20 @@ export class MockUtxoApi implements UtxoApiContract { }); } } - if (!seenUntilBlock) { + if (!seenUntilBlock || lastFoundBestBlock == null) { return { result: UtxoApiResult.BESTBLOCK_ROLLBACK }; } return { result: UtxoApiResult.SUCCESS, - value: { diffItems: utxoDiffItems }, + value: { + diffItems: utxoDiffItems, + reference: { + lastFoundBestBlock, + lastFoundSafeBlock: afterBestBlocks.length > 1 ? afterBestBlocks[1] : undefined, + } + }, }; } } diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js index 161fa6b502..31b3f1a61f 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js @@ -368,7 +368,7 @@ export function getMockServer(settings: { }); server.post('/api/v2/txs/utxoDiffSincePoint', async (req, res) => { - const { addresses, untilBlockHash, afterPoint, diffLimit } = req.body; + const { addresses, untilBlockHash, afterPoint, afterBestblocks } = req.body; if (afterPoint.lastPage) { res.send({ diffItems: [], @@ -383,7 +383,7 @@ export function getMockServer(settings: { { addresses, untilBlockHash, - afterBestBlock: afterPoint.blockHash + afterBestBlocks: afterBestblocks }, ); if (result !== 'SUCCESS') { From 1f2ba3a330a68dafc569363a4826da4ce5934fd2 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Fri, 2 Dec 2022 19:17:24 +0300 Subject: [PATCH 189/199] mock server fix --- .../yoroi-extension/features/mock-chain/mockCardanoServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js index 31b3f1a61f..e646f9e37f 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js @@ -383,7 +383,7 @@ export function getMockServer(settings: { { addresses, untilBlockHash, - afterBestBlocks: afterBestblocks + afterBestBlocks: afterBestblocks || [afterPoint.blockHash], }, ); if (result !== 'SUCCESS') { From ee6b568ef431fd15178176eec2ce61a38969a77f Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Fri, 2 Dec 2022 19:44:53 +0300 Subject: [PATCH 190/199] mock server fix --- .../app/api/ada/lib/state-fetch/mockNetwork.js | 4 ++-- .../yoroi-extension/features/mock-chain/mockCardanoServer.js | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js index d174b8d65d..03f5518888 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js @@ -371,7 +371,7 @@ export function genUtxoSumForAddresses( export function getSingleAddressString( mnemonic: string, path: Array, - isLedger?: boolean = false, + isLedger: boolean = false, ): string { const bip39entropy = mnemonicToEntropy(mnemonic); const EMPTY_PASSWORD = Buffer.from(''); @@ -413,7 +413,7 @@ export function getMangledAddressString( mnemonic: string, path: Array, stakingKey: Buffer, - isLedger?: boolean = false, + isLedger: boolean = false, ): string { const bip39entropy = mnemonicToEntropy(mnemonic); const EMPTY_PASSWORD = Buffer.from(''); diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js index e646f9e37f..693da39ae8 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js @@ -409,6 +409,7 @@ export function getMockServer(settings: { tx_index: item.utxo.txIndex, }; }); + const { lastFoundBestBlock, lastFoundSafeBlock } = (value: any).reference; res.send({ // no pagination, always return all at once diffItems, @@ -417,6 +418,10 @@ export function getMockServer(settings: { lastDiffPointSelected: { lastPage: true, }, + ...(afterBestblocks ? { + lastFoundSafeblock: lastFoundSafeBlock, + lastFoundBestblock: lastFoundBestBlock, + } : {}) }); } }); From ee5ca6ab6c5e4370436986cc52a1a55f1a1f6348 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 6 Dec 2022 18:46:19 +0300 Subject: [PATCH 191/199] Version bump: 4.18.0 --- packages/yoroi-extension/package-lock.json | 2 +- packages/yoroi-extension/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index 674abed53c..bd36274daa 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.17.001", + "version": "4.18.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index af710bd6f1..cd7115482d 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.17.001", + "version": "4.18.0", "description": "Cardano ADA wallet", "scripts": { "dev:build": "rimraf dev/ && babel-node scripts/build --type=debug", From 355de7ec4c41a535256faa041a3eed46597d5f91 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 6 Dec 2022 23:10:42 +0300 Subject: [PATCH 192/199] nft metadata handling fix --- .../app/components/wallet/send/WalletSendFormRevamp.js | 5 +++-- .../app/containers/wallet/NFTDetailPageRevamp.js | 9 +++++---- .../app/containers/wallet/NFTsPageRevamp.js | 5 +++-- packages/yoroi-extension/app/utils/formatters.js | 5 ++--- packages/yoroi-extension/app/utils/wallet.js | 5 +++-- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js index eecc7d391d..454dbd68d6 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormRevamp.js @@ -364,10 +364,11 @@ export default class WalletSendForm extends Component { ({ token }) => token.IsNFT === true ).map(({ token }) => { const policyId = token.Identifier.split('.')[0]; - const name = truncateToken(getTokenStrictName(token) ?? '-'); + const fullName = getTokenStrictName(token); + const name = truncateToken(fullName ?? '-'); return { name, - image: getImageFromTokenMetadata(policyId, name, token.Metadata), + image: getImageFromTokenMetadata(policyId, fullName, token.Metadata), info: token, }; }); diff --git a/packages/yoroi-extension/app/containers/wallet/NFTDetailPageRevamp.js b/packages/yoroi-extension/app/containers/wallet/NFTDetailPageRevamp.js index 779de6fdb9..1c85862872 100644 --- a/packages/yoroi-extension/app/containers/wallet/NFTDetailPageRevamp.js +++ b/packages/yoroi-extension/app/containers/wallet/NFTDetailPageRevamp.js @@ -59,7 +59,8 @@ class NFTDetailPageRevamp extends Component { .filter(item => item.info.IsNFT) .map(token => { const policyId = token.entry.identifier.split('.')[0]; - const name = truncateToken(getTokenStrictName(token.info) ?? '-'); + const fullName = getTokenStrictName(token.info); + const name = truncateToken(fullName ?? '-'); return { policyId, name, @@ -67,13 +68,13 @@ class NFTDetailPageRevamp extends Component { ticker: token.info.Metadata.ticker ?? '-', assetName: token.entry.identifier.split('.')[1] ?? '', id: getTokenIdentifierIfExists(token.info) ?? '-', - image: getImageFromTokenMetadata(policyId, name, token.info.Metadata), + image: getImageFromTokenMetadata(policyId, fullName, token.info.Metadata), description: getDescriptionFromTokenMetadata( policyId, - name, + fullName, token.info.Metadata ), - author: getAuthorFromTokenMetadata(policyId, name, token.info.Metadata), + author: getAuthorFromTokenMetadata(policyId, fullName, token.info.Metadata), // $FlowFixMe metadata: token.info.Metadata?.assetMintMetadata?.[0] || null, }; diff --git a/packages/yoroi-extension/app/containers/wallet/NFTsPageRevamp.js b/packages/yoroi-extension/app/containers/wallet/NFTsPageRevamp.js index 8c052aee27..22bae17f64 100644 --- a/packages/yoroi-extension/app/containers/wallet/NFTsPageRevamp.js +++ b/packages/yoroi-extension/app/containers/wallet/NFTsPageRevamp.js @@ -39,13 +39,14 @@ export default class NFTsPageRevamp extends Component item.info.IsNFT) .map(token => { const policyId = token.entry.identifier.split('.')[0]; - const name = truncateToken(getTokenStrictName(token.info) ?? '-'); + const fullName = getTokenStrictName(token.info); + const name = truncateToken(fullName ?? '-'); return { name, id: getTokenIdentifierIfExists(token.info) ?? '-', image: getImageFromTokenMetadata( policyId, - name, + fullName, token.info.Metadata, ), }; diff --git a/packages/yoroi-extension/app/utils/formatters.js b/packages/yoroi-extension/app/utils/formatters.js index 47b5039e05..907b5584c6 100644 --- a/packages/yoroi-extension/app/utils/formatters.js +++ b/packages/yoroi-extension/app/utils/formatters.js @@ -73,11 +73,10 @@ export const formattedAmountWithoutTrailingZeros = (amount: string): string => ( function truncateFormatter(addr: string, cutoff: number): string { const shortener = '...'; - - if (addr.length + shortener.length <= cutoff) { + if (addr.length - shortener.length <= cutoff) { return addr; } - return addr.substring(0, cutoff / 2) + '...' + addr.substring(addr.length - (cutoff / 2), addr.length); + return addr.substring(0, cutoff / 2) + shortener + addr.substring(addr.length - (cutoff / 2), addr.length); } export function truncateToken(addr: string): string { diff --git a/packages/yoroi-extension/app/utils/wallet.js b/packages/yoroi-extension/app/utils/wallet.js index 5edb2bf57b..d220faa136 100644 --- a/packages/yoroi-extension/app/utils/wallet.js +++ b/packages/yoroi-extension/app/utils/wallet.js @@ -63,11 +63,12 @@ export const getNFTs: GetNFTFunc = (spendableBalance, getTokenInfo) => { .filter(token => token.info.IsNFT) .map(token => { const policyId = token.entry.identifier.split('.')[0]; - const name = truncateToken(getTokenStrictName(token.info) ?? '-'); + const fullName = getTokenStrictName(token.info); + const name = truncateToken(fullName ?? '-'); return { name, id: getTokenIdentifierIfExists(token.info) ?? '-', - image: getImageFromTokenMetadata(policyId, name, token.info.Metadata), + image: getImageFromTokenMetadata(policyId, fullName, token.info.Metadata), info: token.info, }; }); From 6e2d14a138b12b0d3bdb410c18a65963308b36fb Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 6 Dec 2022 23:54:56 +0300 Subject: [PATCH 193/199] networks selecting update --- .../option-dialog/PickCurrencyOptionDialog.js | 53 +++++++------------ .../app/containers/wallet/WalletAddPage.js | 2 +- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/add/option-dialog/PickCurrencyOptionDialog.js b/packages/yoroi-extension/app/components/wallet/add/option-dialog/PickCurrencyOptionDialog.js index dd80233675..522b81ef5a 100644 --- a/packages/yoroi-extension/app/components/wallet/add/option-dialog/PickCurrencyOptionDialog.js +++ b/packages/yoroi-extension/app/components/wallet/add/option-dialog/PickCurrencyOptionDialog.js @@ -95,42 +95,27 @@ export default class PickCurrencyOptionDialog extends Component { } /> - - {intl.formatMessage(messages.testnetDescription)}
    -
    this.props.onExternalLinkClick(event)} - > - {intl.formatMessage(globalMessages.learnMore)} - - } - /> } - {(!environment.isProduction() || environment.isNightly() || environment.isTest()) && - - {intl.formatMessage(messages.testnetDescription)}
    - this.props.onExternalLinkClick(event)} - > - {intl.formatMessage(globalMessages.learnMore)} - - } - /> - } + {/* */} + {/*{(!environment.isProduction() || environment.isNightly() || environment.isTest()) &&*/} + {/* */} + {/* {intl.formatMessage(messages.testnetDescription)}
    */} + {/* this.props.onExternalLinkClick(event)}*/} + {/* >*/} + {/* {intl.formatMessage(globalMessages.learnMore)}*/} + {/* */} + {/* }*/} + {/* />*/} + {/*}*/} {this.props.onErgo != null && { onCardano={() => actions.profile.setSelectedNetwork.trigger(networks.CardanoMainnet)} onCardanoTestnet={() => actions.profile.setSelectedNetwork.trigger(networks.CardanoTestnet)} onCardanoPreprodTestnet={() => actions.profile.setSelectedNetwork.trigger(networks.CardanoPreprodTestnet)} - onErgo={uiDialogs.isOpen(WalletConnectHWOptionDialog) + onErgo={(uiDialogs.isOpen(WalletConnectHWOptionDialog) || uiDialogs.isOpen(WalletCreateOptionDialog)) ? undefined : () => actions.profile.setSelectedNetwork.trigger(networks.ErgoMainnet)} onAlonzoTestnet={ From 72db62f24ded8266f37f880dc3089d565d825299 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 7 Dec 2022 00:15:26 +0300 Subject: [PATCH 194/199] migration version update --- .../yoroi-extension/app/api/ada/lib/storage/adaMigration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js b/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js index 1ff68cc541..5d48977bd4 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/adaMigration.js @@ -119,7 +119,7 @@ export async function migrateToLatest( ['<3.9.6', async () => { return await ergoTxHistoryReset(persistentDb); }], - ['<4.14', async () => { + ['<4.18', async () => { return await populateNewUtxodata(persistentDb); }], ]; From 08d222dbbf1a2e009bcbd4bca3fa5a60999a16d5 Mon Sep 17 00:00:00 2001 From: yushi Date: Wed, 7 Dec 2022 15:11:01 +0800 Subject: [PATCH 195/199] fix tests --- .../app/api/ada/lib/state-fetch/mockNetwork.js | 6 +++++- .../features/mock-chain/mockCardanoServer.js | 8 +++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js index 03f5518888..66e69920b3 100644 --- a/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js +++ b/packages/yoroi-extension/app/api/ada/lib/state-fetch/mockNetwork.js @@ -873,6 +873,7 @@ export class MockUtxoApi implements UtxoApiContract { let seenUntilBlock = false; const utxoDiffItems = []; let lastFoundBestBlock = null; + let lastFoundSafeBlock = null; for (let i = this.blockchain.length - 1; i >= 0; i--) { const tx = this.blockchain[i]; if (tx.tx_state !== 'Successful') { @@ -887,6 +888,9 @@ export class MockUtxoApi implements UtxoApiContract { if (txInAfterBestblocks && lastFoundBestBlock == null) { lastFoundBestBlock = tx.block_hash; } + if (txInAfterBestblocks && lastFoundSafeBlock == null && i <= this._getLastSafeBlockTxIndex()) { + lastFoundSafeBlock = tx.block_hash; + } if (seenUntilBlock) { if (txInAfterBestblocks) { @@ -948,7 +952,7 @@ export class MockUtxoApi implements UtxoApiContract { diffItems: utxoDiffItems, reference: { lastFoundBestBlock, - lastFoundSafeBlock: afterBestBlocks.length > 1 ? afterBestBlocks[1] : undefined, + ...(lastFoundSafeBlock == null ? {} : { lastFoundSafeBlock }), } }, }; diff --git a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js index 693da39ae8..029a0522ee 100644 --- a/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js +++ b/packages/yoroi-extension/features/mock-chain/mockCardanoServer.js @@ -369,7 +369,7 @@ export function getMockServer(settings: { server.post('/api/v2/txs/utxoDiffSincePoint', async (req, res) => { const { addresses, untilBlockHash, afterPoint, afterBestblocks } = req.body; - if (afterPoint.lastPage) { + if (afterPoint && afterPoint.lastPage) { res.send({ diffItems: [], lastDiffPointSelected: { @@ -418,10 +418,8 @@ export function getMockServer(settings: { lastDiffPointSelected: { lastPage: true, }, - ...(afterBestblocks ? { - lastFoundSafeblock: lastFoundSafeBlock, - lastFoundBestblock: lastFoundBestBlock, - } : {}) + lastFoundSafeblock: lastFoundSafeBlock, + lastFoundBestblock: lastFoundBestBlock, }); } }); From 5e1a973d5e85a3d1a1b536d9056c5d0b13d620d8 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 7 Dec 2022 13:39:07 +0300 Subject: [PATCH 196/199] img csp update --- packages/yoroi-extension/chrome/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/chrome/constants.js b/packages/yoroi-extension/chrome/constants.js index 49f80c2b52..ddf68e7fe3 100644 --- a/packages/yoroi-extension/chrome/constants.js +++ b/packages/yoroi-extension/chrome/constants.js @@ -74,6 +74,6 @@ export function genCSP(request: {| `object-src 'self' ${objectSrc.join(' ')};`, `connect-src ${connectSrc.join(' ')};`, `style-src * ${evalStyle} 'self' ${styleSrc.join(' ')} blob:;`, - `img-src 'self' ${imgSrc.join(' ')} data: ;`, + `img-src 'self' ${imgSrc.join(' ')} https: data: ;`, ].join(' '); } From f75e1e847bfa5e2e9ff7ee655a0ab6a91d2a7810 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 8 Dec 2022 16:20:11 +0300 Subject: [PATCH 197/199] fixing ipfs resolving --- .../app/components/wallet/assets/NFTDetails.js | 3 ++- .../app/components/wallet/assets/NFTsList.js | 3 ++- .../wallet/send/WalletSendFormSteps/AssetsDropdown.js | 3 ++- .../wallet/send/WalletSendFormSteps/NFTImage.js | 5 +++-- packages/yoroi-extension/app/coreUtils.js | 8 ++++++++ 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/assets/NFTDetails.js b/packages/yoroi-extension/app/components/wallet/assets/NFTDetails.js index 1ac348bd8a..38284c19ba 100644 --- a/packages/yoroi-extension/app/components/wallet/assets/NFTDetails.js +++ b/packages/yoroi-extension/app/components/wallet/assets/NFTDetails.js @@ -19,6 +19,7 @@ import type { NetworkRow, CardanoAssetMintMetadata } from '../../../api/ada/lib/ import { NftImage } from './NFTsList'; import { isCardanoHaskell } from '../../../api/ada/lib/storage/database/prepackaged/networks'; import { truncateAddress, truncateAddressShort } from '../../../utils/formatters'; +import { urlResolveIpfs } from '../../../coreUtils'; // Overwrite current theme // Temporary solution untill the new design system. @@ -324,7 +325,7 @@ function NFTDetails({ }} onClick={onClose} > - {nftInfo.name} + {nftInfo.name} diff --git a/packages/yoroi-extension/app/components/wallet/assets/NFTsList.js b/packages/yoroi-extension/app/components/wallet/assets/NFTsList.js index 450b0855f8..bc278e73f5 100644 --- a/packages/yoroi-extension/app/components/wallet/assets/NFTsList.js +++ b/packages/yoroi-extension/app/components/wallet/assets/NFTsList.js @@ -25,6 +25,7 @@ import { Link } from 'react-router-dom'; import { ROUTES } from '../../../routes-config'; import { useState, useEffect } from 'react'; import globalMessages from '../../../i18n/global-messages'; +import { urlResolveIpfs } from '../../../coreUtils'; type Props = {| list: Array<{| id: string, name: string, image: string | null |}>, @@ -183,7 +184,7 @@ export function NftImage({ imageUrl, name, width, height }: {| |}): Node { const [loading, setLoading] = useState(true); const [error, setError] = useState(false); - let url = imageUrl !== null ? imageUrl.replace('ipfs://', 'https://ipfs.io/ipfs/'): null; + const url = urlResolveIpfs(imageUrl); useEffect(() => { if (url !== null) diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/AssetsDropdown.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/AssetsDropdown.js index ef413a2f0a..df01f8cd69 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/AssetsDropdown.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/AssetsDropdown.js @@ -10,6 +10,7 @@ import { intlShape } from 'react-intl'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; import { ReactComponent as DefaultNFTIcon }from '../../../../assets/images/nft-no.inline.svg'; import type { FormattedNFTDisplay, FormattedTokenDisplay, } from '../../../../utils/wallet'; +import { urlResolveIpfs } from '../../../../coreUtils'; type Props = {| +tokens: FormattedTokenDisplay[], @@ -56,7 +57,7 @@ export default class AssetsDropdown extends Component { return (
    - {image ? {nft.name} : } + {image ? {nft.name} : }

    {nft.name}

    diff --git a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/NFTImage.js b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/NFTImage.js index 2a8098a5c1..dbff8e6324 100644 --- a/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/NFTImage.js +++ b/packages/yoroi-extension/app/components/wallet/send/WalletSendFormSteps/NFTImage.js @@ -4,6 +4,7 @@ import { Component } from 'react'; import { ReactComponent as DefaultNFT } from '../../../../assets/images/nft-no.inline.svg'; import { checkNFTImage } from '../../../../utils/wallet'; import type { Node } from 'react'; +import { urlResolveIpfs } from '../../../../coreUtils'; type Props = {| name: string, @@ -26,7 +27,7 @@ export default class NFTImage extends Component { componentDidMount() { const { image } = this.props; if (image === null) return - const imageUrl = image.replace('ipfs://', 'https://ipfs.io/ipfs/'); + const imageUrl = urlResolveIpfs(image); checkNFTImage( imageUrl, () => { this.setState({ loading: false, error: false }) }, @@ -38,7 +39,7 @@ export default class NFTImage extends Component { const { image, name, width, height } = this.props; const { loading, error } = this.state; if (image === null || error) return - const imageUrl = image.replace('ipfs://', 'https://ipfs.io/ipfs/'); + const imageUrl = urlResolveIpfs(image); return ( loading ? ( diff --git a/packages/yoroi-extension/app/coreUtils.js b/packages/yoroi-extension/app/coreUtils.js index 7e46e16fec..24f9052bca 100644 --- a/packages/yoroi-extension/app/coreUtils.js +++ b/packages/yoroi-extension/app/coreUtils.js @@ -20,3 +20,11 @@ export function logErr(f: () => T, msg: (string | (Error) => string)): T { throw e; } } + +/** + * In case the URL is at the IPFS protocol it will be resolved into HTTPS. + * In any other case there will be no change in the returned result. + */ +export function urlResolveIpfs(url?: string): ?string { + return url?.replace('ipfs://', 'https://ipfs.io/ipfs/'); +} From b42c1fab6fb061eafceecba16551a546e83d8b89 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 8 Dec 2022 17:03:44 +0300 Subject: [PATCH 198/199] flow fixes --- .../app/components/wallet/WalletBackupDialog.js | 2 +- packages/yoroi-extension/app/coreUtils.js | 3 ++- packages/yoroi-extension/app/utils/nftMetadata.js | 12 ++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/yoroi-extension/app/components/wallet/WalletBackupDialog.js b/packages/yoroi-extension/app/components/wallet/WalletBackupDialog.js index 8f5df08787..0e53b82786 100644 --- a/packages/yoroi-extension/app/components/wallet/WalletBackupDialog.js +++ b/packages/yoroi-extension/app/components/wallet/WalletBackupDialog.js @@ -25,7 +25,7 @@ type Props = {| index: number, |}>, +onCancelBackup: void => void, - +onTogglePrivacyNotice: void => void, + +togglePrivacyNotice: void => void, +onContinue: void => void, +onBack: void => void, +onStartWalletBackup: void => void, diff --git a/packages/yoroi-extension/app/coreUtils.js b/packages/yoroi-extension/app/coreUtils.js index 24f9052bca..0b897199b0 100644 --- a/packages/yoroi-extension/app/coreUtils.js +++ b/packages/yoroi-extension/app/coreUtils.js @@ -25,6 +25,7 @@ export function logErr(f: () => T, msg: (string | (Error) => string)): T { * In case the URL is at the IPFS protocol it will be resolved into HTTPS. * In any other case there will be no change in the returned result. */ -export function urlResolveIpfs(url?: string): ?string { +export function urlResolveIpfs(url: T): T { + // $FlowFixMe return url?.replace('ipfs://', 'https://ipfs.io/ipfs/'); } diff --git a/packages/yoroi-extension/app/utils/nftMetadata.js b/packages/yoroi-extension/app/utils/nftMetadata.js index 986e3c618f..df4d8a28bc 100644 --- a/packages/yoroi-extension/app/utils/nftMetadata.js +++ b/packages/yoroi-extension/app/utils/nftMetadata.js @@ -82,10 +82,10 @@ export function find721metadata( export function getImageFromTokenMetadata( policyId: string, - name: string, + name: string | void, tokenMetadata: TokenMetadata, ): string | null { - if (tokenMetadata.type !== 'Cardano') { + if (tokenMetadata.type !== 'Cardano' || name == null) { return null; } const nftMetadata = find721metadata( @@ -111,10 +111,10 @@ export function getImageFromTokenMetadata( export function getAuthorFromTokenMetadata( policyId: string, - name: string, + name: string | void, tokenMetadata: TokenMetadata, ): string | null { - if (tokenMetadata.type !== 'Cardano') { + if (tokenMetadata.type !== 'Cardano' || name == null) { return null; } const nftMetadata = find721metadata( @@ -138,10 +138,10 @@ export function getAuthorFromTokenMetadata( export function getDescriptionFromTokenMetadata( policyId: string, - name: string, + name: string | void, tokenMetadata: TokenMetadata, ): string | null { - if (tokenMetadata.type !== 'Cardano') { + if (tokenMetadata.type !== 'Cardano' || name == null) { return null; } const nftMetadata = find721metadata( From bfd0a145575d7c3cf6614ab6cd98f404b2dde756 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 8 Dec 2022 22:44:34 +0300 Subject: [PATCH 199/199] ergo warning banner added --- .../topbar/banners/TestnetWarningBanner.js | 13 +++++++++++++ .../topbar/banners/TestnetWarningBanner.scss | 18 ++++++++++++++++++ .../app/containers/banners/BannerContainer.js | 7 +++++-- .../containers/profile/ComplexityLevelPage.js | 7 +++++-- .../profile/LanguageSelectionPage.js | 7 +++++-- .../app/containers/profile/TermsOfUsePage.js | 7 +++++-- .../app/containers/profile/UriPromptPage.js | 7 +++++-- .../ergo-connector/components/layout/Layout.js | 2 +- 8 files changed, 57 insertions(+), 11 deletions(-) diff --git a/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.js b/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.js index dc58dac8a3..cb735ea070 100644 --- a/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.js +++ b/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.js @@ -21,6 +21,7 @@ const messages = defineMessages({ type Props = {| isTestnet: boolean, + isErgo: boolean, |}; @observer @@ -57,6 +58,18 @@ export default class TestnetWarningBanner extends Component {
    ); } + if (this.props.isErgo) { + return ( +
    + +
    + NOTE: Unfortunately the Ergo network support will be dropped from Yoroi in the near future. +
    + Please make sure to migrate your Ergo funds and wallets to another application. +
    +
    + ); + } return null; } } diff --git a/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.scss b/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.scss index 549a70f695..8b31396cab 100644 --- a/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.scss +++ b/packages/yoroi-extension/app/components/topbar/banners/TestnetWarningBanner.scss @@ -44,3 +44,21 @@ line-height: 20px; } } + +.ergoWarning { + height: 60px; + display: flex; + justify-content: center; + align-items: center; + background: var(--yoroi-palette-background-banner-warning); + .shelleyTestnetWarningIcon { + margin-right: 9px; + } + .text { + text-transform: uppercase; + color: var(--yoroi-palette-common-white); + font-size: 12px; + letter-spacing: 1px; + line-height: 20px; + } +} diff --git a/packages/yoroi-extension/app/containers/banners/BannerContainer.js b/packages/yoroi-extension/app/containers/banners/BannerContainer.js index 199d805b1e..3d9bac311e 100644 --- a/packages/yoroi-extension/app/containers/banners/BannerContainer.js +++ b/packages/yoroi-extension/app/containers/banners/BannerContainer.js @@ -13,7 +13,7 @@ import environment from '../../environment'; import { ServerStatusErrors } from '../../types/serverStatusErrorType'; import type { ServerStatusErrorType } from '../../types/serverStatusErrorType'; import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index'; -import { isTestnet, isCardanoHaskell } from '../../api/ada/lib/storage/database/prepackaged/networks'; +import { isTestnet, isCardanoHaskell, isErgo } from '../../api/ada/lib/storage/database/prepackaged/networks'; import { Bip44Wallet } from '../../api/ada/lib/storage/models/Bip44Wallet/wrapper'; import { getTokenName, genLookupOrFail } from '../../stores/stateless/tokenHelpers'; import type { TokenInfoMap } from '../../stores/toplevel/TokenInfoStore'; @@ -31,6 +31,9 @@ export default class BannerContainer extends Component )} - + {!environment.isProduction() && } {deprecationBanner} diff --git a/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js b/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js index b58a478eaa..221b8a71f5 100644 --- a/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js +++ b/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js @@ -17,7 +17,7 @@ import type { ComplexityLevelType } from '../../types/complexityLevelType'; import type { ServerStatusErrorType } from '../../types/serverStatusErrorType'; import LocalizableError from '../../i18n/LocalizableError'; import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index'; -import { isTestnet } from '../../api/ada/lib/storage/database/prepackaged/networks'; +import { isErgo, isTestnet } from '../../api/ada/lib/storage/database/prepackaged/networks'; const messages = defineMessages({ title: { @@ -41,8 +41,11 @@ export default class ComplexityLevelPage extends Component + ? : ; const topbarTitle = ( diff --git a/packages/yoroi-extension/app/containers/profile/LanguageSelectionPage.js b/packages/yoroi-extension/app/containers/profile/LanguageSelectionPage.js index eca744062b..30f145aed4 100644 --- a/packages/yoroi-extension/app/containers/profile/LanguageSelectionPage.js +++ b/packages/yoroi-extension/app/containers/profile/LanguageSelectionPage.js @@ -19,7 +19,7 @@ import type { LanguageType } from '../../i18n/translations'; import LocalizableError from '../../i18n/LocalizableError'; import type { ServerStatusErrorType } from '../../types/serverStatusErrorType'; import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index'; -import { isTestnet } from '../../api/ada/lib/storage/database/prepackaged/networks'; +import { isErgo, isTestnet } from '../../api/ada/lib/storage/database/prepackaged/networks'; const messages = defineMessages({ title: { @@ -66,13 +66,16 @@ export default class LanguageSelectionPage extends Component) : undefined; const displayedBanner = generated.stores .serverConnectionStore.checkAdaServerStatus === ServerStatusErrors.Healthy - ? + ? : + ? : ; const topbarTitle = ( diff --git a/packages/yoroi-extension/app/containers/profile/UriPromptPage.js b/packages/yoroi-extension/app/containers/profile/UriPromptPage.js index 4af863dbed..b3168aea66 100644 --- a/packages/yoroi-extension/app/containers/profile/UriPromptPage.js +++ b/packages/yoroi-extension/app/containers/profile/UriPromptPage.js @@ -20,7 +20,7 @@ import globalMessages from '../../i18n/global-messages'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; import type { ServerStatusErrorType } from '../../types/serverStatusErrorType'; import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index'; -import { isTestnet } from '../../api/ada/lib/storage/database/prepackaged/networks'; +import { isErgo, isTestnet } from '../../api/ada/lib/storage/database/prepackaged/networks'; import { trackUriPrompt } from '../../api/analytics'; type GeneratedData = typeof UriPromptPage.prototype.generated; @@ -80,9 +80,12 @@ export default class UriPromptPage extends Component + ? : ; const topbarTitle = ( diff --git a/packages/yoroi-extension/app/ergo-connector/components/layout/Layout.js b/packages/yoroi-extension/app/ergo-connector/components/layout/Layout.js index a854307e23..9d37865073 100644 --- a/packages/yoroi-extension/app/ergo-connector/components/layout/Layout.js +++ b/packages/yoroi-extension/app/ergo-connector/components/layout/Layout.js @@ -31,7 +31,7 @@ export default class Layout extends Component { return (
    - +