Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontends/web/src/components/transactions/transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const Transaction = ({
onShowDetail(internalID);
}
}}>
<div className={styles.txContent}>
<div className={styles.txContent} data-testid="transaction" data-tx-type={type}>
<span className={styles.txIcon}>
<Arrow status={status} type={type} />
</span>
Expand Down
147 changes: 147 additions & 0 deletions frontends/web/tests/send.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* Copyright 2025 Shift Crypto AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { expect } from '@playwright/test';
import { test } from './helpers/fixtures';
import { ServeWallet } from './helpers/servewallet';
import { launchRegtest, setupRegtestWallet, sendCoins, mineBlocks, cleanupRegtest } from './helpers/regtest';
import { ChildProcess } from 'child_process';
import { deleteAccountsFile } from './helpers/fs';

let servewallet: ServeWallet;
let regtest: ChildProcess;

test('Send BTC', async ({ page, host, frontendPort, servewalletPort }, testInfo) => {

await test.step('Start regtest and init wallet', async () => {
regtest = await launchRegtest();
// Give regtest some time to start
await new Promise((resolve) => setTimeout(resolve, 3000));
await setupRegtestWallet();
});


await test.step('Start servewallet', async () => {
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host, testInfo.title, testInfo.project.name, { regtest: true, testnet: false });
await servewallet.start();
});


let recvAdd: string;
await test.step('Grab receive address', async () => {
await page.getByRole('button', { name: 'Test wallet' }).click();
await page.getByRole('button', { name: 'Unlock' }).click();
await page.getByRole('link', { name: 'Bitcoin Regtest Bitcoin' }).click();
await page.getByRole('button', { name: 'Receive RBTC' }).click();
await page.getByRole('button', { name: 'Verify address on BitBox' }).click();
const addressLocator = page.locator('[data-testid="receive-address"]');
recvAdd = await addressLocator.inputValue();
expect(recvAdd).toContain('bcrt1');
console.log(`Receive address: ${recvAdd}`);
Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't there be an expect() call at the end?

maybe it could just test that the recvAdd starts with "brct1" (for regtest addresses but I am not sure).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

shouldn't there be an expect() call at the end?

I guess we could technically do it, even though the step is mostly only trying to grab the address and we are reusing it later

maybe it could just test that the recvAdd starts with "brct1" (for regtest addresses but I am not sure).

I'll do that!

});

await test.step('Verify there are no transactions yet', async () => {
await page.goto('/#/account-summary');
mineBlocks(12);
await page.locator('[data-label="Account name"]').nth(0).click();
await expect(page.getByTestId('transaction')).toHaveCount(0);
});

await test.step('Add second RBTC account', async () => {
await page.goto('/#/account-summary');
await page.getByRole('link', { name: 'Settings' }).click();
await page.getByRole('link', { name: 'Manage Accounts' }).click();
await page.getByRole('button', { name: 'Add account' }).click();
await page.getByRole('button', { name: 'Add account' }).click();
Copy link
Collaborator

Choose a reason for hiding this comment

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

On a multi device you would have to choose the coin type first. So I guess this is a btc-only simulated device?

Just thinking out loud. Is the edition btconly/multi something we should be able to configure for each test in the future?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This test is performed in regtest, which only supports BTC.
To test a multi behaviour, we'd need to use testnet with either a software keystore or a simulator, which are both "multi"

Copy link
Collaborator

Choose a reason for hiding this comment

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

I didn't mean to suggest to test altcoins here, but just wanted to note that if the device was a multi it would have 1 extra step here.

await expect(page.locator('body')).toContainText('Bitcoin Regtest 2 has now been added to your accounts.');
await page.getByRole('button', { name: 'Done' }).click();
Copy link
Collaborator

Choose a reason for hiding this comment

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

At the end of adding a 2nd account there should be a text: "Account added" or "<accountname> has now been added to your accounts."

Maybe something you could test with expect here?

});

await test.step('Send RBTC to receive address', async () => {
console.log('Sending RBTC to first account');
await page.waitForTimeout(2000);
const sendAmount = '10';
sendCoins(recvAdd, sendAmount);
mineBlocks(12);
});

await test.step('Verify that the first account has a transaction', async () => {
await page.goto('/#/account-summary');
await page.locator('[data-label="Account name"]').nth(0).click();
await expect(page.getByTestId('transaction')).toHaveCount(1);

// It should be an incoming tx
const tx = page.getByTestId('transaction').nth(0);
await expect(tx).toHaveAttribute('data-tx-type', 'receive');

});

await test.step('Grab receive address for second account', async () => {
await page.goto('/#/account-summary');
await page.getByRole('link', { name: 'Bitcoin Regtest 2' }).click();

await page.getByRole('button', { name: 'Receive RBTC' }).click();
await page.getByRole('button', { name: 'Verify address on BitBox' }).click();
const addressLocator = page.locator('[data-testid="receive-address"]');
recvAdd = await addressLocator.inputValue();
expect(recvAdd).toContain('bcrt1');
console.log(`Receive address: ${recvAdd}`);
});

await test.step('Send RBTC to second account receive address', async () => {
await page.goto('/#/account-summary');
await page.getByRole('link', { name: 'Bitcoin Regtest Bitcoin' }).click();
console.log('Sending RBTC to second account');
Copy link
Collaborator

Choose a reason for hiding this comment

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

in the run-tests log I can only see, the following

Receive address: bcrt1qn00wa7j8v2ssq7y7774qua7xly02she0ukcygc
Receive address: bcrt1qh8r2rx5myc0pjhkjyx70hug2s4dpj6fufej4jg
Sending RBTC to second account

https://github.com/BitBoxSwiss/bitbox-wallet-app/actions/runs/19498423962/job/55806495741

maybe also add a console log 'Sending RBTC to first account' when sending the first time? Then the log info would be a bit more usefuls.

await page.getByRole('link', { name: 'Send' }).click();
await page.fill('#recipientAddress', recvAdd);
await page.click('#sendAll');
await page.getByRole('button', { name: 'Review' }).click();
await page.getByRole('button', { name: 'Done' }).click();
mineBlocks(12);
});

await test.step('Verify that first account now has two transactions', async () => {
await page.goto('/#/account-summary');
await page.locator('td[data-label="Account name"]').nth(0).click();
await expect(page.getByTestId('transaction')).toHaveCount(2);
// Verify that the second one is outgoing
// Txs are shown in reverse order
const oldTx = page.getByTestId('transaction').nth(1);
await expect(oldTx).toHaveAttribute('data-tx-type', 'receive');

const newTx = page.getByTestId('transaction').nth(0);
await expect(newTx).toHaveAttribute('data-tx-type', 'send');
});

await test.step('Verify that the second account has a transaction', async () => {
await page.goto('/#/account-summary');
await page.locator('td[data-label="Account name"]').nth(1).click();
await expect(page.getByTestId('transaction')).toHaveCount(1);

const tx = page.getByTestId('transaction').nth(0);
await expect(tx).toHaveAttribute('data-tx-type', 'receive');
});

});

test.beforeEach(async () => {
deleteAccountsFile();
});

test.afterAll(async () => {
await servewallet.stop();
await cleanupRegtest(regtest);
});