Skip to content

Commit 0c09c42

Browse files
committed
Merge branch 'regtest'
2 parents 705cce0 + f00d567 commit 0c09c42

File tree

9 files changed

+343
-25
lines changed

9 files changed

+343
-25
lines changed

frontends/web/playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default defineConfig({
1717
timeout: 120_000,
1818
},
1919
],
20-
timeout: 120_000,
20+
timeout: 180_000,
2121
workers: 1, // Tests are not parallel-safe yet.
2222
use: {
2323
baseURL: `http://${HOST}:${FRONTEND_PORT}`,

frontends/web/src/components/amount/amount-with-unit.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export const AmountUnit = ({ rotateUnit, unit, className = '' }: TAmountUnitProp
107107
const classRototable = rotateUnit ? (style.rotatable || '') : '';
108108
const textStyle = `${style.unit || ''} ${classRototable} ${className}`;
109109
return (
110-
<span className={textStyle} onClick={rotateUnit}>
110+
<span data-testid={`amount-unit-${unit}`} className={textStyle} onClick={rotateUnit}>
111111
{unit}
112112
</span>
113113
);

frontends/web/src/components/copy/Copy.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type TProps = {
3131
flexibleHeight?: boolean;
3232
displayValue?: string;
3333
value: string;
34+
dataTestId?: string;
3435
};
3536

3637
export const CopyableInput = ({
@@ -44,6 +45,7 @@ export const CopyableInput = ({
4445
disabled,
4546
flexibleHeight,
4647
displayValue,
48+
dataTestId,
4749
}: TProps) => {
4850
const [success, setSuccess] = useState(false);
4951
const { t } = useTranslation();
@@ -87,6 +89,7 @@ export const CopyableInput = ({
8789
}
8890
};
8991

92+
9093
return (
9194
<div className={[
9295
'flex flex-row flex-start flex-items-start',
@@ -100,6 +103,7 @@ export const CopyableInput = ({
100103
value={displayValue ? displayValue : value}
101104
ref={textAreaRef}
102105
rows={1}
106+
{...(dataTestId ? { 'data-testid': dataTestId } : {})}
103107
className={`${style.inputField || ''}
104108
${flexibleHeight ? style.flexibleHeight || '' : ''}
105109
${alignLeft ? style.alignLeft || '' : ''}

frontends/web/src/routes/account/receive/receive.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,11 @@ export const Receive = ({
324324
<p>{t('receive.verifyInstruction')}</p>
325325
</div>
326326
<div className="m-bottom-half">
327-
<CopyableInput value={address} flexibleHeight />
327+
<CopyableInput
328+
value={address}
329+
dataTestId="receive-address"
330+
flexibleHeight
331+
/>
328332
</div>
329333
</>
330334
)}

frontends/web/tests/banner.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* Copyright 2025 Shift Crypto AG
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { expect, Page } from '@playwright/test';
18+
import { test } from './helpers/fixtures';
19+
import { ServeWallet } from './helpers/servewallet';
20+
import { launchRegtest, setupRegtestWallet, sendCoins, mineBlocks, cleanupRegtest } from './helpers/regtest';
21+
import { ChildProcess } from 'child_process';
22+
23+
let servewallet: ServeWallet;
24+
let regtest: ChildProcess;
25+
26+
test('Backup reminder banner is shown when currency is > 1000', async ({ page, host, frontendPort, servewalletPort }, testInfo) => {
27+
28+
29+
await test.step('Start regtest and init wallet', async () => {
30+
regtest = await launchRegtest();
31+
// Give regtest some time to start
32+
await new Promise((resolve) => setTimeout(resolve, 3000));
33+
await setupRegtestWallet();
34+
});
35+
36+
37+
await test.step('Start servewallet', async () => {
38+
servewallet = new ServeWallet(page, servewalletPort, frontendPort, host, testInfo.title, testInfo.project.name, { regtest: true, testnet: false });
39+
await servewallet.start();
40+
});
41+
42+
let recvAdd: string;
43+
await test.step('Grab receive address', async () => {
44+
await page.getByRole('button', { name: 'Test wallet' }).click();
45+
await page.getByRole('button', { name: 'Unlock' }).click();
46+
await page.getByRole('link', { name: 'Bitcoin Regtest Bitcoin' }).click();
47+
await page.getByRole('button', { name: 'Receive RBTC' }).click();
48+
await page.getByRole('button', { name: 'Verify address on BitBox' }).click();
49+
const addressLocator = page.locator('[data-testid="receive-address"]');
50+
recvAdd = await addressLocator.inputValue();
51+
console.log(`Receive address: ${recvAdd}`);
52+
});
53+
54+
await test.step('Verify that the backup banner is NOT shown initially', async () => {
55+
await page.goto('/');
56+
await verifyBackupBanner(page, undefined, false);
57+
});
58+
59+
await test.step('Send RBTC to receive address', async () => {
60+
await page.waitForTimeout(2000);
61+
const sendAmount = '10';
62+
sendCoins(recvAdd, sendAmount);
63+
mineBlocks(12);
64+
});
65+
66+
await test.step('Verify that the backup banner is shown with the correct currency', async () => {
67+
await page.goto('/');
68+
await page.waitForTimeout(5000);
69+
const units = ['USD', 'EUR', 'CHF'];
70+
let currentIndex = 0;
71+
// First, verify that the banner shows USD by default.
72+
await verifyBackupBanner(page, units[currentIndex]!);
73+
74+
// Then, cycle through the currency units and verify the banner updates accordingly.
75+
for (let i = 0; i < units.length; i++) {
76+
await page.locator(`header [data-testid="amount-unit-${units[currentIndex]!}"]`).click();
77+
const nextIndex = (currentIndex + 1) % units.length;
78+
await page.waitForTimeout(1000); // wait for the UI to update
79+
await verifyBackupBanner(page, units[nextIndex]!);
80+
currentIndex = nextIndex;
81+
}
82+
});
83+
});
84+
85+
// Helper function to verify the banner presence or absence
86+
async function verifyBackupBanner(
87+
page: Page,
88+
expectedCurrency?: string,
89+
shouldExist = true
90+
) {
91+
await test.step(
92+
shouldExist
93+
? `Verify that the backup banner is shown for ${expectedCurrency!}`
94+
: 'Verify that the backup banner is NOT shown',
95+
async () => {
96+
const textContent = await page.textContent('body');
97+
98+
if (shouldExist) {
99+
if (!expectedCurrency) {
100+
throw new Error('Currency must be provided when expecting banner.');
101+
}
102+
103+
const regex = new RegExp(
104+
`Your wallet\\s+Software keystore [a-f0-9]+\\s+passed ${expectedCurrency} 1[’,']000\\.00!`
105+
);
106+
expect(textContent).toMatch(regex);
107+
108+
expect(textContent).toContain(
109+
'We recommend creating a paper backup for extra protection. It\'s quick and simple.'
110+
);
111+
} else {
112+
// Check that the banner text is NOT present
113+
const bannerRegex = /Your wallet Software keystore [a-f0-9]+ passed [A-Z]{3} 1,000\.00!/;
114+
expect(textContent).not.toMatch(bannerRegex);
115+
expect(textContent).not.toContain(
116+
'We recommend creating a paper backup for extra protection. It\'s quick and simple.'
117+
);
118+
}
119+
}
120+
);
121+
}
122+
123+
test.afterAll(async () => {
124+
await servewallet.stop();
125+
await cleanupRegtest(regtest);
126+
});

frontends/web/tests/base.test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { test } from './helpers/fixtures';
1818
import { ServeWallet } from './helpers/servewallet';
1919
import { expect } from '@playwright/test';
20+
import { deleteAccountsFile, deleteConfigFile } from './helpers/fs';
2021

2122
let servewallet: ServeWallet;
2223

@@ -31,10 +32,16 @@ test('App main page loads', async ({ page, host, frontendPort, servewalletPort }
3132
await test.step('Navigate to the app', async () => {
3233
await page.goto(`http://${host}:${frontendPort}`);
3334
const body = page.locator('body');
34-
await expect(body).toContainText('Please connect your BitBox and tap the side to continue.');
35+
await expect(body).toContainText('Please connect your BitBox and tap the side to continue.'),
36+
{ timeout: 15000 };
3537
});
3638
});
3739

38-
test.afterAll(() => {
39-
servewallet.stop();
40+
test.beforeAll(async () => {
41+
deleteAccountsFile();
42+
deleteConfigFile();
43+
});
44+
45+
test.afterAll(async () => {
46+
await servewallet.stop();
4047
});

frontends/web/tests/helpers/dom.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,6 @@
1616

1717
import { Page, Locator, expect } from '@playwright/test';
1818

19-
/**
20-
* Returns a locator for elements matching a given attribute key/value pair.
21-
*
22-
* @param page - Playwright page
23-
* @param attrKey - The attribute key to select (e.g., "data-label")
24-
* @param attrValue - The value of the attribute to match
25-
* @returns Locator for matching elements
26-
*/
27-
export function getFieldsByAttribute(page: Page, attrKey: string, attrValue: string): Locator {
28-
return page.locator(`[${attrKey}="${attrValue}"]`);
29-
}
30-
3119
/**
3220
* Finds elements by attribute key/value and asserts the expected count.
3321
*
@@ -42,7 +30,7 @@ export async function assertFieldsCount(
4230
attrValue: string,
4331
expectedCount: number
4432
) {
45-
const locator = getFieldsByAttribute(page, attrKey, attrValue);
33+
const locator = page.locator(`[${attrKey}="${attrValue}"]`);
4634
await expect(locator).toHaveCount(expectedCount);
4735
}
4836

0 commit comments

Comments
 (0)