diff --git a/cypress.json b/cypress.json index a00047a19..bff15fae1 100644 --- a/cypress.json +++ b/cypress.json @@ -14,5 +14,6 @@ "baseUrl": "http://localhost:25444", "env": { "broadcast_timeout":"8000" - } + }, + "includeShadowDom": false } diff --git a/cypress/integration/spec_elm_multi_segwit_wallet.js b/cypress/integration/spec_elm_multi_segwit_wallet.js index bf76fb9b6..126978197 100644 --- a/cypress/integration/spec_elm_multi_segwit_wallet.js +++ b/cypress/integration/spec_elm_multi_segwit_wallet.js @@ -1,27 +1,26 @@ -describe('Operating with an elements multisig wallet', () => { - +describe('Operating with an Elements multisig wallet', () => { - it('Creates an two elements multisig hot wallets (segwit/nested) both 2/3', () => { + it('Creates two (segwit/nested) Elements multisig (2/3) hot wallets', () => { cy.viewport(1200,660) cy.visit('/') cy.get('#node-switch-icon').click() cy.get('#elements_node-select-node-form > .item > div').click() - // Delete Wallet if existing + // Delete wallets if existing cy.deleteWallet("Elm Multi Segwit Wallet") cy.deleteWallet("Elm Multi Nested Wallet") - // Create the Hot Element Wallets - cy.addHotDevice("Hot Elements Device 2","elements") - cy.addHotDevice("Hot Elements Device 3","elements") - cy.addHotDevice("Hot Elements Device 4","elements") + // Add devices for multisig + cy.addHotDevice("Elm Multisig Device 1","elements") + cy.addHotDevice("Elm Multisig Device 2","elements") + cy.addHotDevice("Elm Multisig Device 3","elements") // Create Segwit multisig wallet cy.get('#btn_new_wallet').click() cy.get('[href="./multisig/"]').click() - cy.get('#hot_elements_device_2').click() - cy.get('#hot_elements_device_3').click() - cy.get('#hot_elements_device_4').click() + cy.get('#elm_multisig_device_1').click() + cy.get('#elm_multisig_device_2').click() + cy.get('#elm_multisig_device_3').click() cy.get('#submit-device').click() cy.get('#wallet_name').type("Elm Multi Segwit Wallet") cy.get(':nth-child(9) > .inline').clear() @@ -34,193 +33,156 @@ describe('Operating with an elements multisig wallet', () => { //Get some funds cy.mine2wallet("elm") - // Create Nested multisig wallet cy.get('#btn_new_wallet').click() cy.get('[href="./multisig/"]').click() - cy.get('#hot_elements_device_2').click() - cy.get('#hot_elements_device_3').click() - cy.get('#hot_elements_device_4').click() + cy.get('#elm_multisig_device_1').click() + cy.get('#elm_multisig_device_2').click() + cy.get('#elm_multisig_device_3').click() cy.get('#submit-device').click() - cy.get(':nth-child(1) > #type_nested_segwit_btn') // Nested!! + // Switch to Nested Segwit + cy.get('#type_nested_segwit_btn').click() cy.get('#wallet_name').type("Elm Multi Nested Wallet") + // 2 of 3 cy.get(':nth-child(9) > .inline').clear() - // 2 of 2 cy.get(':nth-child(9) > .inline').type("2") // submit cy.get('#keysform > .centered').click() - // Cancel-button (no pdf download) + // Click cancel (no pdf download) cy.get('#page_overlay_popup_cancel_button').click() - //Get some funds + // Get some funds cy.mine2wallet("elm") }) - it('Spending to a Confidential address from segwit', () => { + it('Spending to a confidential address from segwit', () => { + cy.viewport(1200,660) + cy.visit('/') + cy.contains("Elm Multi Segwit Wallet").click() + + cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }).then(($div) => { + // Create PSBT + const oldBalance = parseFloat($div.text()) + expect(oldBalance).to.be.gte(1.5) + cy.createPsbt("el1qqdsywea5scrn7t9q83fd540pw447h0uae30pdp82rzgkl7yzvjz6gra9ls8qu6sslw4s0ck48we06zhqd6kwjy2quh69zwxwn", "Burn address","1.5") + + // First signature + cy.get('#elm_multisig_device_1_tx_sign_btn').click() + cy.get('#elm_multisig_device_1_hot_sign_btn').click() + cy.contains('Sign transaction').click() + + // Second signature + cy.get('#elm_multisig_device_2_tx_sign_btn').click() + cy.get('#elm_multisig_device_2_hot_sign_btn').click() + cy.contains('Sign transaction').click() + + // Send the tx + cy.get('#broadcast_local_btn').click() + + // Redirect to "transactions", check balance there + cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }) + .should(($div) => { + const newBalance = parseFloat($div.text()) + expect(newBalance).to.be.lte(oldBalance - 1.5) + }) + }) + }) + + it('Spending to an unnconfidential address from segwit', () => { cy.viewport(1200,660) cy.visit('/') - // spend the money again cy.contains("Elm Multi Segwit Wallet").click() - cy.get('#fullbalance_amount').then(($div) => { + cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }).then(($div) => { + // Create PSBT + const oldBalance = parseFloat($div.text()) + expect(oldBalance).to.be.gte(1.5) + cy.createPsbt("ert1q38la37ulxgc0uwt334he46eua7h8qagqnlm5phcqk7ntgv3x73cqjtr2fa", "unconf Burn address","1.5") + + // First signature + cy.get('#elm_multisig_device_1_tx_sign_btn').click() + cy.get('#elm_multisig_device_1_hot_sign_btn').click() + cy.contains('Sign transaction').click() + + // Second signature + cy.get('#elm_multisig_device_2_tx_sign_btn').click() + cy.get('#elm_multisig_device_2_hot_sign_btn').click() + cy.contains('Sign transaction').click() + + // Send tx + cy.get('#broadcast_local_btn').click() + + // Redirect to "transactions", check balance there + cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }) + .should(($div) => { + const newBalance = parseFloat($div.text()) + expect(newBalance).to.be.lte(oldBalance - 1.5) + }) + }) + }) + + it('Spending to a confidential address from nested', () => { + cy.viewport(1200,660) + cy.visit('/') + cy.contains("Elm Multi Nested Wallet").click() + + cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }).then(($div) => { + // Create PSBT const oldBalance = parseFloat($div.text()) expect(oldBalance).to.be.gte(1.5) cy.createPsbt("el1qqdsywea5scrn7t9q83fd540pw447h0uae30pdp82rzgkl7yzvjz6gra9ls8qu6sslw4s0ck48we06zhqd6kwjy2quh69zwxwn", "Burn address","1.5") + // First signature + cy.get('#elm_multisig_device_1_tx_sign_btn').click() + cy.get('#elm_multisig_device_1_hot_sign_btn').click() + cy.contains('Sign transaction').click() - cy.get('#hot_elements_device_2_tx_sign_btn').click() - cy.get('#hot_elements_device_2_hot_sign_btn').click() - cy.get('#hot_enter_passphrase__submit').click() - cy.get('#hot_elements_device_3_tx_sign_btn').click() - cy.get('#hot_elements_device_3_hot_sign_btn').click() - cy.get('#hot_enter_passphrase__submit').click() - // cy.get('#broadcast_local_btn').click() - // There is a bug as the first device doesn't seem to have signed and so we can't send the tx just right from here - // 2 more clicks until to get to the send button - cy.get('#btn_send').click() - cy.get('.row > :nth-child(1) > .btn').click() - cy.get('#send_tx_btn').click() + // Second signature + cy.get('#elm_multisig_device_2_tx_sign_btn').click() + cy.get('#elm_multisig_device_2_hot_sign_btn').click() + cy.contains('Sign transaction').click() + + // Send the tx cy.get('#broadcast_local_btn').click() - // gets redirected to "transactions" + // Redirect to "transactions", check balance there cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }) .should(($div) => { const newBalance = parseFloat($div.text()) expect(newBalance).to.be.lte(oldBalance - 1.5) }) }) - - // Workaround: Transaction does not disappear - cy.get('#btn_send').click() - // The "delete" button in the first psbt - cy.get('.row > :nth-child(2) > .btn').click() - - }) - it('Spending to a Unconfidential address from segwit', () => { - + it('Spending to a unconfidential address from nested', () => { cy.viewport(1200,660) cy.visit('/') - // spend the money again - cy.contains("Elm Multi Segwit Wallet").click() + cy.contains("Elm Multi Nested Wallet").click() - cy.get('#fullbalance_amount').then(($div) => { + cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }).then(($div) => { + // Create PSBT const oldBalance = parseFloat($div.text()) expect(oldBalance).to.be.gte(1.5) cy.createPsbt("ert1q38la37ulxgc0uwt334he46eua7h8qagqnlm5phcqk7ntgv3x73cqjtr2fa", "unconf Burn address","1.5") + // First signature + cy.get('#elm_multisig_device_1_tx_sign_btn').click() + cy.get('#elm_multisig_device_1_hot_sign_btn').click() + cy.contains('Sign transaction').click() - cy.get('#hot_elements_device_2_tx_sign_btn').click() - cy.get('#hot_elements_device_2_hot_sign_btn').click() - cy.get('#hot_enter_passphrase__submit').click() - cy.get('#hot_elements_device_3_tx_sign_btn').click() - cy.get('#hot_elements_device_3_hot_sign_btn').click() - cy.get('#hot_enter_passphrase__submit').click() - // cy.get('#broadcast_local_btn').click() - // There is a bug as the first device doesn't seem to have signed and so we can't send the tx just right from here - // 2 more clicks until to get to the send button - cy.get('#btn_send').click() - cy.get('.row > :nth-child(1) > .btn').click() - cy.get('#send_tx_btn').click() + // Second signature + cy.get('#elm_multisig_device_2_tx_sign_btn').click() + cy.get('#elm_multisig_device_2_hot_sign_btn').click() + cy.contains('Sign transaction').click() + + // Send the tx cy.get('#broadcast_local_btn').click() - // gets redirected to "transactions" + // Redirect to "transactions", check balance there cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }) .should(($div) => { const newBalance = parseFloat($div.text()) expect(newBalance).to.be.lte(oldBalance - 1.5) }) }) - - // Workaround: Transaction does not disappear - // Strange bug, not needed here - // cy.get('#btn_send').click() - // The "delete" button in the first psbt - // cy.get('.row > :nth-child(2) > .btn').click() - - }) - - it('Spending to a Confidential address from nested', () => { - cy.viewport(1200,660) - cy.visit('/') - // spend the money again - cy.contains("Elm Multi Nested Wallet").click() - - cy.get('#fullbalance_amount').then(($div) => { - const oldBalance = parseFloat($div.text()) - expect(oldBalance).to.be.gte(1.5) - cy.createPsbt("el1qqdsywea5scrn7t9q83fd540pw447h0uae30pdp82rzgkl7yzvjz6gra9ls8qu6sslw4s0ck48we06zhqd6kwjy2quh69zwxwn", "Burn address","1.5") - - - cy.get('#hot_elements_device_2_tx_sign_btn').click() - cy.get('#hot_elements_device_2_hot_sign_btn').click() - cy.get('#hot_enter_passphrase__submit').click() - cy.get('#hot_elements_device_3_tx_sign_btn').click() - cy.get('#hot_elements_device_3_hot_sign_btn').click() - cy.get('#hot_enter_passphrase__submit').click() - // cy.get('#broadcast_local_btn').click() - // There is a bug as the first device doesn't seem to have signed and so we can't send the tx just right from here - // 2 more clicks until to get to the send button - cy.get('#btn_send').click() - cy.get('.row > :nth-child(1) > .btn').click() - cy.get('#send_tx_btn').click() - cy.get('#broadcast_local_btn').click() - // gets redirected to "transactions" - - cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }) - .should(($div) => { - const newBalance = parseFloat($div.text()) - expect(newBalance).to.be.lte(oldBalance - 1.5) - }) - }) - - // Workaround: Transaction does not disappear - cy.get('#btn_send').click() - // The "delete" button in the first psbt - cy.get('.row > :nth-child(2) > .btn').click() - - - }) - - it('Spending to a Unconfidential address from nested', () => { - - cy.viewport(1200,660) - cy.visit('/') - // spend the money again - cy.contains("Elm Multi Nested Wallet").click() - - cy.get('#fullbalance_amount').then(($div) => { - const oldBalance = parseFloat($div.text()) - expect(oldBalance).to.be.gte(1.5) - cy.createPsbt("ert1q38la37ulxgc0uwt334he46eua7h8qagqnlm5phcqk7ntgv3x73cqjtr2fa", "unconf Burn address","1.5") - - - cy.get('#hot_elements_device_2_tx_sign_btn').click() - cy.get('#hot_elements_device_2_hot_sign_btn').click() - cy.get('#hot_enter_passphrase__submit').click() - cy.get('#hot_elements_device_3_tx_sign_btn').click() - cy.get('#hot_elements_device_3_hot_sign_btn').click() - cy.get('#hot_enter_passphrase__submit').click() - // cy.get('#broadcast_local_btn').click() - // There is a bug as the first device doesn't seem to have signed and so we can't send the tx just right from here - // 2 more clicks until to get to the send button - cy.get('#btn_send').click() - cy.get('.row > :nth-child(1) > .btn').click() - cy.get('#send_tx_btn').click() - cy.get('#broadcast_local_btn').click() - // gets redirected to "transactions" - cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }) - .should(($div) => { - const newBalance = parseFloat($div.text()) - expect(newBalance).to.be.lte(oldBalance - 1.5) - }) - }) - - // Workaround: Transaction does not disappear - // Strange bug, not needed here - // cy.get('#btn_send').click() - // The "delete" button in the first psbt - // cy.get('.row > :nth-child(2) > .btn').click() - }) }) \ No newline at end of file diff --git a/cypress/integration/spec_node_configured.js b/cypress/integration/spec_node_configured.js index d4215a355..0b79787d1 100644 --- a/cypress/integration/spec_node_configured.js +++ b/cypress/integration/spec_node_configured.js @@ -5,10 +5,10 @@ describe('Node Configured', () => { cy.addDevice("Testdevice Ghost") cy.get('body').then(($body) => { if ($body.text().includes('Testwallet Ghost')) { - cy.get('#wallets_list > .item > svg').click() - cy.get(':nth-child(6) > .right').click() + cy.contains('Testwallet Ghost').click() + cy.get('#btn_settings').click({"force": true}) cy.get('#advanced_settings_tab_btn').click() - cy.get('.card > :nth-child(9) > .btn').click() + cy.get('#delete_wallet').click() } }) cy.get('#btn_new_wallet').click() diff --git a/cypress/integration/spec_plugins.js b/cypress/integration/spec_plugins.js index e9b457028..88f49d0c9 100644 --- a/cypress/integration/spec_plugins.js +++ b/cypress/integration/spec_plugins.js @@ -7,7 +7,7 @@ describe('plugins are working', () => { // choose address cy.get('#test_hot_wallet_1-sidebar-list-item').click() cy.get('[href="/wallets/wallet/test_hot_wallet_1/addresses/"]').click() - cy.get('addresses-table').shadow().find('address-row').eq(0).shadow().find('.explorer-link').click() + cy.get('addresses-table').shadow().find('address-row').eq(0).shadow().find('.explorer-link').eq(0).click({position: 'top'}) cy.get('address-data').shadow().find('#associate-btn').click() cy.contains("Service integration requires an authentication method that includes a password") @@ -20,7 +20,7 @@ describe('plugins are working', () => { // choose address again cy.get('#test_hot_wallet_1-sidebar-list-item').click() cy.get('[href="/wallets/wallet/test_hot_wallet_1/addresses/"]').click() - cy.get('addresses-table').shadow().find('address-row').eq(0).shadow().find('.explorer-link').click() + cy.get('addresses-table').shadow().find('address-row').eq(0).shadow().find('.explorer-link').eq(0).click() cy.get('address-data').shadow().find('#associate-btn').click() cy.contains("Associating an address with a service will") diff --git a/cypress/integration/spec_wallet_utxo.js b/cypress/integration/spec_wallet_utxo.js index 52f00e11c..3b1354878 100644 --- a/cypress/integration/spec_wallet_utxo.js +++ b/cypress/integration/spec_wallet_utxo.js @@ -1,82 +1,150 @@ -describe('Send transactions from wallets', () => { - it('Freeze and unfreeze UTXO', () => { - const name = "UTXO Hot Bitcoin3" - const wallet_name = name+" wallet" +describe('Test the actions in UTXO list', () => { + + before(() => { + Cypress.config('includeShadowDom', true) + const device_name = "UTXO device" + const wallet_name = "UTXO wallet" var wallet_name_ref = wallet_name.toLowerCase().replace(/ /g,"_") cy.viewport(1200,660) cy.visit('/') - cy.addHotDevice(name+" device","bitcoin") - cy.addHotWallet(wallet_name,name+" device", "bitcoin", "segwit") - cy.get('#fullbalance_amount').then(($div) => { - const balance = parseFloat($div.text()) - if ( balance <= 20) { + cy.get('#toggle_devices_list').click() + cy.addHotDevice(device_name, "bitcoin") + cy.addHotWallet(wallet_name, device_name, "bitcoin", "segwit") + cy.get('#fullbalance_amount').then(($span) => { + const balance = parseFloat($span.text()) + if (balance <= 20) { cy.log("balance " + balance + " too low. Mining!") cy.mine2wallet("btc") cy.mine2wallet("btc") cy.mine2wallet("btc") + cy.mine2wallet("btc") + // We are having 5 UTXO in the end } }) - - cy.contains(wallet_name).click() + }) - // The table as component is only available through the shadow tree - // That's why we have this stupid .shadow() ... - cy.get('tx-table').shadow().find('.utxo-view-btn').click({ force: true }) + // Keeps the session cookie alive, Cypress by default clears all cookies before each test + beforeEach(() => { + Cypress.Cookies.preserveOnce('session') + }) - cy.log("Check that nothis is frozen") - cy.get('tx-table').shadow().find('tx-row').each(($el, index, $list) => { - cy.wrap($el).shadow().find('.tx-row').should('not.have.class', 'frozen') - cy.wrap($el).shadow().find('.frozen-img').should('have.class', 'hidden') - }) - - - cy.log("First select it, then freeze it") - cy.get('tx-table').shadow().find('tx-row').eq(0).shadow().find('.select-tx-img').click() + it('Freezing', () => { + cy.contains("UTXO wallet").click() + cy.get('tx-table').find('.utxo-view-btn').click({ force: true }) + cy.wait(500) + // Freeze 3 UTXOs + cy.log("Freeze 3 UTXO") + cy.get('tx-table').find('tx-row').eq(0).find('.select-tx-img').click( {position: 'top'} ) + cy.get('tx-table').find('tx-row').eq(0).find('.select-tx-value').invoke('attr', 'value').should('eq', 'true') // Check whether click worked cy.wait(100) - // then click the freeze-button - cy.get('tx-table').shadow().find('.freeze-tx-btn').click() + cy.get('tx-table').find('tx-row').eq(1).find('.select-tx-img').click( {position: 'top'} ) + cy.get('tx-table').find('tx-row').eq(1).find('.select-tx-value').invoke('attr', 'value').should('eq', 'true') cy.wait(100) - - cy.get('tx-table').shadow().find('tx-row').each(($el, index, $list) => { - if (index == 0) { - cy.wrap($el).shadow().find('.tx-row').should('have.class', 'frozen') - cy.wrap($el).shadow().find('.frozen-img').should('not.have.class', 'hidden') + cy.get('tx-table').find('tx-row').eq(3).find('.select-tx-img').click( {position: 'top'} ) + cy.get('tx-table').find('tx-row').eq(3).find('.select-tx-value').invoke('attr', 'value').should('eq', 'true') + cy.wait(100) + cy.get('tx-table').find('.freeze-tx-btn').click() + cy.wait(500) + // We should have 3 frozen outputs now, let's check that + cy.log("Check whether we have 3 frozen outputs") + cy.get('tx-table').find('tx-row').each(($el, index, $list) => { + if (index == 0 || index == 1 || index == 3) { + cy.wrap($el).find('.tx-row').should('have.class', 'frozen') + cy.wrap($el).find('.frozen-img').should('not.have.class', 'hidden') } else { - cy.wrap($el).shadow().find('.tx-row').should('not.have.class', 'frozen') - cy.wrap($el).shadow().find('.frozen-img').should('have.class', 'hidden') + cy.wrap($el).find('.tx-row').should('not.have.class', 'frozen') + cy.wrap($el).find('.frozen-img').should('have.class', 'hidden') } }) + // Check that we can't create a transaction when a frozen UTXO is selected ... + cy.log("Check that we can't create a transaction when frozen UTXO is selected") + cy.get('tx-table').find('tx-row').eq(2).find('.select-tx-img').click( {position: 'top'} ) + cy.get('tx-table').find('tx-row').eq(0).find('.select-tx-img').click( {position: 'top'} ) + cy.get('tx-table').find('.compose-tx-btn').should('be.hidden') + // ... and that the checkboxes disappear for non-frozen UTXO and get unticked + cy.log("... and that the checkboxes disappear for non-frozen UTXO and get unticked") + cy.get('tx-table').find('tx-row').eq(2).find('.select-tx-img').should('be.hidden') + cy.get('tx-table').find('tx-row').eq(2).find('.select-tx-img').should('have.attr', 'src').and('contain', 'untick') + cy.get('tx-table').find('tx-row').eq(4).find('.select-tx-img').should('be.hidden') + cy.get('tx-table').find('tx-row').eq(4).find('.select-tx-img').should('have.attr', 'src').and('contain', 'untick') + // Unselect the first UTXO again and check that all checkboxes are visible and none are selected + cy.log("Unselect the first UTXO again and check that all checkboxes are visible and none are selected") + cy.get('tx-table').find('tx-row').eq(0).find('.select-tx-img').click( {position: 'top'} ) + cy.wait(100) + cy.get('tx-table').find('tx-row').each(($el, index, $list) => { + cy.wrap($el).find('.tx-row').find('.select-tx-img').should('not.be.hidden').should('have.attr', 'src').and('contain', 'untick') + }) + }) - // Test freeze UTXO can't be spend, and unfreeze works for coin selection option - cy.log("Select 3 UTXOs and freeze them") - cy.get('tx-table').shadow().find('tx-row').eq(0).shadow().find('.select-tx-img').click() - cy.get('tx-table').shadow().find('tx-row').eq(1).shadow().find('.select-tx-img').click() - cy.get('tx-table').shadow().find('tx-row').eq(3).shadow().find('.select-tx-img').click() - cy.get('tx-table').shadow().find('.compose-tx-btn').click() + it('Creating a transaction', () => { + // Make a transaction with the third and forth UTXO + cy.log("Make a transaction with the third and forth UTXO") + // Unfreeze necessary for the forth UTXO + cy.get('tx-table').find('tx-row').eq(3).find('.select-tx-img').click( {position: 'top'} ) + cy.wait(100) + cy.get('tx-table').find('.freeze-tx-btn').click() + cy.wait(200) + cy.get('tx-table').find('tx-row').eq(2).find('.select-tx-img').click( {position: 'top'} ); + cy.get('tx-table').find('tx-row').eq(2).find('.select-tx-value').invoke('attr', 'value').should('eq', 'true') // Check that the click flow is (still) in order + cy.get('tx-table').find('tx-row').eq(3).find('.select-tx-img').click( {position: 'top'} ) + cy.get('tx-table').find('tx-row').eq(3).find('.select-tx-value').invoke('attr', 'value').should('eq', 'true') + cy.wait(100) + cy.get('tx-table').find('.compose-tx-btn').should('not.be.hidden') + cy.get('tx-table').find('.compose-tx-btn').click() + // Switch to coin selection, check that the right amount of coins are preselected in the coin selection + cy.log("Switch to coin selection, check that the right amount of coins are preselected in the coin selection") + cy.get('.coinselect-hidden').should('have.length', 2); + }) - // If you select a coin from the utxo-set and cklick on "create transaction", the coins need to be preselected - cy.get('.coinselect-hidden').should('have.length', 3); - - // Unfreeze the UTXO + it('Managing unsigned transactions', () => { + // Make an unsigned tx + cy.get('#address_0').type("bcrt1q74tj43hrg550vdtkhsmkrfjqspkku8zes0vq34") // some address from testwallet ghost + cy.get('#send_max_0').click() + cy.get('#create_psbt_btn').click() + // Check the labeling of the unsigned UTXO + cy.log("Check the labeling of the unsigned UTXO") cy.get('#btn_transactions').click() cy.wait(1000) - cy.get('tx-table').shadow().find('.utxo-view-btn').click() - - cy.get('tx-table').shadow().find('tx-row').eq(0).shadow().find('.select-tx-img').click() - cy.get('tx-table').shadow().find('.freeze-tx-btn').click() - - cy.get('tx-table').shadow().find('tx-row').each(($el, index, $list) => { - cy.wrap($el).shadow().find('.tx-row').should('not.have.class', 'frozen') - cy.wrap($el).shadow().find('.frozen-img').should('have.class', 'hidden') + cy.get('tx-table').find('.utxo-view-btn').click() + cy.get('tx-table').find('tx-row').eq(2).find('#column-category').should('contain', 'Unsigned') + cy.get('tx-table').find('tx-row').eq(3).find('#column-category').should('contain', 'Unsigned') + // Check that only the two checkboxes of the unsigned UTXO are visible + cy.log("Check that only the two checkboxes of the unsigned UTXO are visible") + cy.get('tx-table').find('tx-row').eq(2).find('.select-tx-img').click( {position: 'top'} ) + cy.wait(200) + cy.get('tx-table').find('tx-row').each(($el, index, $list) => { + if (index == 0 || index == 1) { + cy.wrap($el).find('.tx-row').find('.select-tx-img').should('be.hidden').should('have.attr', 'src').and('contain', 'untick') + } }) - - // Ensure selection of unfreeze now works - cy.get('tx-table').shadow().find('tx-row').eq(0).shadow().find('.select-tx-img').click() - cy.get('tx-table').shadow().find('tx-row').eq(1).shadow().find('.select-tx-img').click() - cy.get('tx-table').shadow().find('tx-row').eq(3).shadow().find('.select-tx-img').click() - cy.get('tx-table').shadow().find('.compose-tx-btn').click() - - cy.get('.coinselect-hidden').should('have.length', 3); - + // Unselect the third UTXO again and check that all checkboxes are visible and none are selected + cy.log("Unselect the third UTXO again and check that all checkboxes are visible and none are selected") + cy.get('tx-table').find('tx-row').eq(2).find('.select-tx-img').click( {position: 'top'} ) + cy.get('tx-table').find('tx-row').eq(2).find('.select-tx-value').invoke('attr', 'value').should('eq', '') + cy.wait(100) + cy.get('tx-table').find('tx-row').each(($el, index, $list) => { + cy.wrap($el).find('.tx-row').find('.select-tx-img').should('not.be.hidden') + }) + // Select a normal UTXO, select an unsigned UTXO and then unselect the unsigned again + cy.log("Select a normal UTXO, select an unsigned UTXO and then unselect the unsigned again") + cy.get('tx-table').find('tx-row').eq(4).find('.select-tx-img').click( {position: 'top'} ) + cy.get('tx-table').find('tx-row').eq(4).find('.select-tx-value').invoke('attr', 'value').should('eq', 'true') + cy.wait(100) + cy.get('tx-table').find('tx-row').eq(3).find('.select-tx-img').click( {position: 'top'} ) + cy.get('tx-table').find('tx-row').eq(3).find('.select-tx-value').invoke('attr', 'value').should('eq', 'true') + cy.wait(100) + cy.get('tx-table').find('tx-row').eq(3).find('.select-tx-img').click( {position: 'top'} ) + cy.get('tx-table').find('tx-row').eq(3).find('.select-tx-value').invoke('attr', 'value').should('eq', '') + cy.wait(100) + // Check that all checkboxes are visible again and none are selected + cy.get('tx-table').find('tx-row').each(($el, index, $list) => { + cy.wrap($el).find('.tx-row').find('.select-tx-img').should('not.be.hidden').should('have.attr', 'src').and('contain', 'untick') + }) + // Check that the manage PSBT button is working + cy.get('tx-table').find('tx-row').eq(2).find('.select-tx-img').click( {position: 'top'} ) + cy.wait(100) + cy.get('tx-table').find('#manage-psbt-btn').should('not.be.hidden').click() + cy.wait(200) + cy.contains("Here you can manage PSBTs") }) }) \ No newline at end of file diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index cf36ed907..4483601a3 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -48,6 +48,18 @@ module.exports = (on, config) => { } }) + // For future use, not used yet + on('task', { + 'delete:bitcoin-hotwallet': (name) => { + console.log('connection details: %s', btc_conn) + const bitcoin_data_dir=btc_conn["bitcoin_data_dir"]; + var rimraf = require("rimraf"); + console.log('Removing all wallets in %s', bitcoin_data_dir+"/regtest/wallets/specter123456_hotstorage") + rimraf.sync(bitcoin_data_dir+"/regtest/wallets/specter123456_hotstorage"); + return null + } + }) + on('task', { 'btc:mine': () => { // sending the bitcoind-process a signal SIGUSR1 (10) will cause mining towards all specter-wallets diff --git a/cypress/support/commands.js b/cypress/support/commands.js index ae940e3eb..ef99c3f0b 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -63,10 +63,11 @@ Cypress.Commands.add("addDevice", (name) => { Cypress.Commands.add("addHotDevice", (name, node_type) => { // node_type is either elements or bitcoin cy.get('body').then(($body) => { - //cy.task("delete:elements-hotwallet") + cy.task("delete:elements-hotwallet") if ($body.text().includes(name)) { + cy.get('#toggle_devices_list').click() var refName = "#device_list_item_"+name.toLowerCase().replace(/ /g,"_") - cy.get(refName).click() + cy.get(refName).click( {force: true} ) cy.get('#forget_device').click() // We might get an error here, if the device is used in a wallet // We assume therefore that this is ok (see below) @@ -75,7 +76,7 @@ Cypress.Commands.add("addHotDevice", (name, node_type) => { if (!cy.get('#btn_new_device').isVisible) { cy.get('#toggle_devices_list').click() } - cy.get('#btn_new_device').click() + cy.get('#btn_new_device').click( {force: true} ) cy.contains('Select Your Device Type') cy.get(`#${node_type}core_device_card`).click() cy.get('#submit-mnemonic').click() @@ -102,9 +103,7 @@ Cypress.Commands.add("addHotWallet", (wallet_name, device_name, node_type, walle cy.get('#advanced_settings_tab_btn').click() cy.get('#delete_wallet').click() } - cy.get('#side-content').click() - cy.get('#btn_new_wallet').click() cy.get('[href="./simple/"]').click() var device_button = "#"+device_name.toLowerCase().replace(/ /g,"_") @@ -131,7 +130,7 @@ Cypress.Commands.add("deleteWallet", (name) => { cy.get('body').then(($body) => { if ($body.text().includes(name)) { cy.contains(name).click() - cy.get('#btn_settings').click() + cy.get('#btn_settings').click( {force: true} ) cy.get('#advanced_settings_tab_btn').click() cy.get('#delete_wallet').click() // That does not seem to delete the wallet-file in elements, though @@ -144,8 +143,8 @@ Cypress.Commands.add("deleteWallet", (name) => { Cypress.Commands.add("mine2wallet", (chain) => { // Fund it and check the balance cy.get('#btn_transactions').click() - cy.get('#fullbalance_amount').then(($div) => { - const oldBalance = parseFloat($div.text()) + cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }).then(($span) => { + const oldBalance = parseFloat($span.text()) if (chain=="elm" || chain=="elements") { cy.task("elm:mine") } else if (chain=="btc" || chain=="bitcoin") { @@ -154,8 +153,8 @@ Cypress.Commands.add("mine2wallet", (chain) => { throw new Error("Unknown chain: " + chain) } cy.waitUntil( () => cy.reload().get('#fullbalance_amount', { timeout: 3000 }) - .then(($div) => { - const n = parseFloat($div.text()) + .then(($span) => { + const n = parseFloat($span.text()) return n > oldBalance }) , { diff --git a/src/cryptoadvance/specter/server_endpoints/filters.py b/src/cryptoadvance/specter/server_endpoints/filters.py index be8ca0612..d5e46dd34 100644 --- a/src/cryptoadvance/specter/server_endpoints/filters.py +++ b/src/cryptoadvance/specter/server_endpoints/filters.py @@ -32,7 +32,7 @@ def timedatetime(context, s): def btcamount(context, value): if value is None: return "Unknown" - if value < 0: + if value < 0 and app.specter.is_liquid: return "Confidential" value = round(float(value), 8) return "{:,.8f}".format(value).rstrip("0").rstrip(".") @@ -63,7 +63,7 @@ def btcunitamount(context, value): return "#########" if value is None: return "Unknown" - if value < 0: + if value < 0 and app.specter.is_liquid: return "Confidential" if app.specter.unit != "sat": return btcamount(context, value) diff --git a/src/cryptoadvance/specter/server_endpoints/wallets.py b/src/cryptoadvance/specter/server_endpoints/wallets.py index af1fd5b23..b2f65513a 100644 --- a/src/cryptoadvance/specter/server_endpoints/wallets.py +++ b/src/cryptoadvance/specter/server_endpoints/wallets.py @@ -361,7 +361,7 @@ def new_wallet(wallet_type): @login_required def wallet(wallet_alias): wallet = app.specter.wallet_manager.get_by_alias(wallet_alias) - if wallet.fullbalance > 0: + if wallet.amount_total > 0: return redirect(url_for("wallets_endpoint.history", wallet_alias=wallet_alias)) else: return redirect(url_for("wallets_endpoint.receive", wallet_alias=wallet_alias)) @@ -468,6 +468,15 @@ def send_new(wallet_alias): rbf_utxo = [] rbf_tx_id = "" selected_coins = request.form.getlist("coinselect") + # Additional server side check not to use frozen UTXO as a precaution + frozen_utxo = wallet.frozen_utxo + for utxo in selected_coins: + if utxo in frozen_utxo: + selected_coins.remove(utxo) + flash(f"You've selected a frozen UTXO for a transaction.", "error") + return redirect( + url_for("wallets_endpoint.history", wallet_alias=wallet_alias) + ) if request.method == "POST": action = request.form.get("action") @@ -601,11 +610,7 @@ def send_new(wallet_alias): flash(_("Failed to get RBF coins. Error: {}").format(e), "error") show_advanced_settings = ( - ui_option != "ui" - or subtract - or fee_options != "dynamic" - or not rbf - or selected_coins + ui_option != "ui" or subtract or fee_options != "dynamic" or not rbf ) wallet_utxo = wallet.utxo if app.specter.is_liquid: diff --git a/src/cryptoadvance/specter/server_endpoints/wallets_api.py b/src/cryptoadvance/specter/server_endpoints/wallets_api.py index a8b239ef6..6fa86a336 100644 --- a/src/cryptoadvance/specter/server_endpoints/wallets_api.py +++ b/src/cryptoadvance/specter/server_endpoints/wallets_api.py @@ -439,6 +439,14 @@ def wallets_overview_utxo_list(): ) +@wallets_endpoint_api.route("/wallet//pending_psbt_list", methods=["GET"]) +@login_required +def pending_psbt_list(wallet_alias): + wallet = app.specter.wallet_manager.get_by_alias(wallet_alias) + pending_psbts = wallet.pending_psbts_dict() + return jsonify(pending_psbts=pending_psbts) + + @wallets_endpoint_api.route("/wallet//addresses_list/", methods=["POST"]) @login_required @app.csrf.exempt @@ -676,11 +684,11 @@ def asset_balances(wallet_alias): textUnit = app.specter.unit asset_balances = { "btc": { - "balance": wallet.full_available_balance, + "balance": wallet.amount_available, "label": label, }, "sat": { - "balance": int(wallet.full_available_balance * 1e8), + "balance": int(wallet.amount_available * 1e8), "label": "sat", }, } diff --git a/src/cryptoadvance/specter/static/styles.css b/src/cryptoadvance/specter/static/styles.css index d531c4093..d24834288 100644 --- a/src/cryptoadvance/specter/static/styles.css +++ b/src/cryptoadvance/specter/static/styles.css @@ -141,16 +141,6 @@ ul, li{ /*max-width: 100%;*/ justify-content: center; } -tr.unconfirmed{ - background: var(--cmap-bg); - /*font-style: italic;*/ -} -.unconfirmed td img{ - opacity: 0.7; -} -.unconfirmed:hover td img{ - opacity: 1; -} .row{ display: flex; flex-direction: row; @@ -215,7 +205,6 @@ main{ /*justify-content: center;*/ padding: 0; /*padding: 30px 0 20px 0;*/ - overflow-y: scroll; } nav.side > .item, nav.side > div > .item{ padding: 10px 0; @@ -520,6 +509,24 @@ input:hover{ .flex-item{ margin: 0.5em; } +.smaller-card{ + min-width: 220px; + padding: 15px; + background: var(--cmap-bg-lighter); + border: 2px solid var(--cmap-border); + border-radius: 4px; + color: #fff; + font-size: 0.9em; + text-align: center; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + margin: 5px; +} +.smaller-card:hover{ + border: 2px solid hsl(217, 15%, 55%); +} .small-card{ min-width: 220px; /*height: 250px;*/ @@ -707,8 +714,6 @@ table{ .table-holder{ padding: 0 30px; width: 100%; - overflow-y: scroll; - /*overflow-x: scroll;*/ } .full-width{ width: 100%; @@ -741,29 +746,74 @@ tbody tr:hover{ .tx{ max-width: 200px; } -.scroll{ + +/* Scroll functionality along x axis + (mainly used for xpub keys) +*/ + +.scroll { padding: 0 10px; } -.scroll > *{ +.scroll > * { /* ugly fix for firefox - scrolling child element */ display: block; overflow-x: scroll; width: 100%; padding: 20px 0; } -.scroll > *::-webkit-scrollbar, -#side-content::-webkit-scrollbar, -main::-webkit-scrollbar, -pre::-webkit-scrollbar{ - display: none; -} -/* Hide scrollbar for IE, Edge and Firefox */ -td.scroll > *, -#side-content, -main,pre{ +/* Hide scrollbar thumb and track for IE, Edge and Firefox */ +td.scroll > *, pre { -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ - scrollbar-height: none; /* Firefox */ +} +/* Hide scrollbar thumb and track for Chrome */ +td.scroll > *::-webkit-scrollbar, +pre::-webkit-scrollbar { + display: none; +} + +/* Standard scrollbar along y axis +scrollbar-color and scrollbar-width only supported by Firefox */ + +main { + overflow-y: scroll; + scrollbar-color: hsl(217, 10%, 80%) var(--cmap-border); + scrollbar-width: thin; +} +/* Not supported by Firefox & Safari on iOS */ +main::-webkit-scrollbar { + width: 0.5em; +} +main::-webkit-scrollbar:horizontal { + display: none; +} +main::-webkit-scrollbar-track { + background: var(--cmap-bg); + border-radius: 100vw; + margin-top: 3.5em; + margin-bottom: 1em; +} +main::-webkit-scrollbar-track:active { + background: hsl(217, 15%, 55%); + border-radius: 100vw; + margin-top: 3.5em; + margin-bottom: 1em; +} +main::-webkit-scrollbar-thumb { + background: hsl(217, 10%, 80%); + border-radius: 100vw; +} +main::-webkit-scrollbar-thumb:hover { + background: white; +} + +/* No scrollbar for side-content */ +#side-content::-webkit-scrollbar { + display: none; +} + +#side-content { + scrollbar-width: none; } table a, .address-link{ diff --git a/src/cryptoadvance/specter/templates/base.jinja b/src/cryptoadvance/specter/templates/base.jinja index 82be34343..7ae428db6 100644 --- a/src/cryptoadvance/specter/templates/base.jinja +++ b/src/cryptoadvance/specter/templates/base.jinja @@ -65,7 +65,7 @@ {% endblock %}


{% if current_user.is_authenticated and not hwi_bridge %} -
+ @@ -26,11 +30,11 @@ - Internally, a tx-table is used which is customizable in order to be usefull here. + Internally, a tx-table is used which is customizable in order to be useful here. We're using the asset_balance endpoint to get Liquid assets. */ class CoinSelection extends HTMLElement { @@ -71,16 +75,12 @@ if (this.getAttribute("is-liquid") == "True") { this.classList.add("hidden") } - - this.txtable.setAttribute("wallet", this.walletAlias) - let selectedCoins = JSON.parse(this.getAttribute('selected-coins').replace(/'/g, '"')); if (selectedCoins.length > 0) { this.txtable.setAttribute("selected-coins", JSON.stringify(selectedCoins)) // unfold if we have preselected coins this.toggleCoinselection() } - } /** @@ -117,7 +117,7 @@ let spendableAmount; if (!this.isCoinSelectionActive()) { if(unit == 'btc' || unit == 'sat'){ - spendableAmount = '{{ wallet.full_available_balance }}'; + spendableAmount = '{{ wallet.amount_available }}'; } else { if (unit in assetBalances){ return assetBalances[unit].balance; @@ -155,6 +155,7 @@ this.txtable.getSelectedTxs().forEach( (tx) => { sum = sum + tx.amount }) + sum = Number.parseFloat(sum.toFixed(8)) return sum } diff --git a/src/cryptoadvance/specter/templates/includes/page-limit-select.html b/src/cryptoadvance/specter/templates/includes/page-limit-select.html index 75a4191e3..cba85b54f 100644 --- a/src/cryptoadvance/specter/templates/includes/page-limit-select.html +++ b/src/cryptoadvance/specter/templates/includes/page-limit-select.html @@ -6,6 +6,7 @@ .page-limit-fieldset { float: left; border: none; + margin-top: 4px; /*to better vertically align on tx history / utxo list overview*/ display: inline; } .page-limit-fieldset > select { diff --git a/src/cryptoadvance/specter/templates/includes/tx-row.html b/src/cryptoadvance/specter/templates/includes/tx-row.html index badba0654..6544d518c 100644 --- a/src/cryptoadvance/specter/templates/includes/tx-row.html +++ b/src/cryptoadvance/specter/templates/includes/tx-row.html @@ -1,8 +1,5 @@ @@ -69,7 +130,10 @@ this.el = clone.querySelector(".tx-row"); this.callbackaction = clone.querySelector(".callbackaction"); + this.coinSelectCheckbox = clone.getElementById("coin-select-checkbox"); + this.categoryCell = clone.getElementById("column-category"); this.category = clone.querySelector(".category"); + this.iconInfoText = clone.getElementById("icon-info-text"); this.txid = clone.querySelector(".txid .explorer-link"); this.address = clone.querySelector(".address"); this.serviceIcon = clone.querySelector(".service-icon"); @@ -97,15 +161,15 @@ this.symbol = this.getAttribute('data-symbol'); this.mode = this.getAttribute('data-mode'); this.hideSensitiveInfo = this.getAttribute('data-hide-sensitive-info') == 'true'; - - // Set data for customaction - this.callbackaction.childNodes[1].value=`${this.tx['txid']},${this.tx['vout']}` - this.callbackaction.childNodes[1].amount = `${this.tx['amount']}` // a bit hackish, doing that to have access via this in next line - this.callbackaction.childNodes[1].addEventListener("change", (e) => { - let event = new CustomEvent('txRowCustomSelected', { detail: { - txid: this.tx.txid, + // Listening for changes of coin selection checkboxes + this.coinSelectCheckbox.value=`${this.tx['txid']},${this.tx['vout']}` // Necessary? + this.coinSelectCheckbox.amount = `${this.tx['amount']}` // a bit hackish, doing that to have access via this in next line - necessary? + this.coinSelectCheckbox.addEventListener("change", (e) => { + let event = new CustomEvent(('coinSelectRowSelected'), { detail: { + txid: this.tx.txid, // Detail currently not used, remove? vout: this.tx.vout, - amount: this.amount + amount: this.amount, + locked: this.tx.locked, } } ); this.dispatchEvent(event); @@ -257,8 +321,6 @@ } } - - // Show blockhash if (this.showBlockhash) { this.blockhash.classList.remove('hidden'); @@ -285,7 +347,8 @@ selected: this.selectTxValue.value == "true", txid: this.tx.txid, vout: this.tx.vout, - amount: this.amount + amount: this.amount, + locked: this.tx.locked, } }); this.dispatchEvent(event); } @@ -315,15 +378,15 @@ } isSelected() { - return this.callbackaction.childNodes[1].checked + return this.coinSelectCheckbox.checked } unselect() { - this.callbackaction.childNodes[1].checked = false + this.coinSelectCheckbox.checked = false } select() { - this.callbackaction.childNodes[1].checked = true + this.coinSelectCheckbox.checked = true } rbfPopup(rbfType) { @@ -376,9 +439,10 @@ case "send": return `{{ url_for('static', filename='img') }}/send.svg`; case "receive": + return `{{ url_for('static', filename='img') }}/receive.svg`; case "immature": case "generate": - return `{{ url_for('static', filename='img') }}/receive.svg`; + return `{{ url_for('static', filename='img') }}/gear.svg`; case "selftransfer": return `{{ url_for('static', filename='img') }}/transfer.svg`; case "mixed": diff --git a/src/cryptoadvance/specter/templates/includes/tx-table.html b/src/cryptoadvance/specter/templates/includes/tx-table.html index cf6584a23..ca1df50c7 100644 --- a/src/cryptoadvance/specter/templates/includes/tx-table.html +++ b/src/cryptoadvance/specter/templates/includes/tx-table.html @@ -1,9 +1,49 @@ + +