Skip to content

Commit 5cd0f09

Browse files
authored
test: add encrypted key import automation test (#3362)
Add automation test 'Verify user can import an encrypted private key'. Signed-off-by: Simon Viénot <simon.vienot@icloud.com>
1 parent db6620b commit 5cd0f09

6 files changed

Lines changed: 97 additions & 2 deletions

File tree

automation/pages/SettingsPage.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export class SettingsPage extends BasePage {
5656
importButtonSelector = 'button-restore-dropdown';
5757
ed25519ImportLinkSelector = 'link-import-ed25519-key';
5858
ecdsaImportLinkSelector = 'link-import-ecdsa-key';
59+
encryptedKeyImportLinkSelector = 'link-import-encrypted-key';
5960
ed25519ImportButtonSelector = 'button-ed25519-private-key-import';
6061
ecdsaImportButtonSelector = 'button-ecdsa-private-key-import';
6162
decryptMainPrivateKeyButtonSelector = 'span-show-modal-0';
@@ -77,6 +78,11 @@ export class SettingsPage extends BasePage {
7778
importPublicKeyDropdownButtonSelector = 'button-import-public-dropdown';
7879
importSinglePublicKeyButtonSelector = 'import-single-public-key';
7980
importPublicKeyButtonSelector = 'button-public-key-import';
81+
browseEncryptedKeysButtonSelector = 'button-browse-encrypted-keys';
82+
importEncryptedKeysButtonSelector = 'button-import-encrypted-keys';
83+
decryptKeysPasswordInputSelector = 'input-decrypt-keys-password';
84+
decryptEncryptedKeysButtonSelector = 'button-decrypt-encrypted-keys';
85+
skipImportRecoveryPhraseButtonSelector = 'button-skip-import-recovery-phrase';
8086
deletePublicKeyMappingButtonSelector = 'button-delete-public-key-mapping';
8187
selectAllPublicKeysCheckboxSelector = 'checkbox-select-all-public-keys';
8288
deleteAllPublicKeysButtonSelector = 'button-delete-public-all';
@@ -431,6 +437,30 @@ export class SettingsPage extends BasePage {
431437
await this.click(this.importButtonSelector);
432438
}
433439

440+
async clickOnEncryptedKeysDropdown(): Promise<void> {
441+
await this.click(this.encryptedKeyImportLinkSelector);
442+
}
443+
444+
async clickOnBrowseEncryptedKeyButton(): Promise<void> {
445+
await this.click(this.browseEncryptedKeysButtonSelector);
446+
}
447+
448+
async clickOnImportEncryptedKeyButton(): Promise<void> {
449+
await this.click(this.importEncryptedKeysButtonSelector);
450+
}
451+
452+
async fillInDecryptKeysPassword(password: string): Promise<void> {
453+
await this.fill(this.decryptKeysPasswordInputSelector, password);
454+
}
455+
456+
async clickOnDecryptEncryptedKeyButton(): Promise<void> {
457+
await this.click(this.decryptEncryptedKeysButtonSelector);
458+
}
459+
460+
async clickOnSkipImportRecoveryPhraseButton(): Promise<void> {
461+
await this.click(this.skipImportRecoveryPhraseButtonSelector);
462+
}
463+
434464
async clickOnECDSADropDown(): Promise<void> {
435465
await this.click(this.ecdsaImportLinkSelector);
436466
}

automation/tests/helpers/fixtures/settingsKeysSuite.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export function setupSettingsKeysSuite() {
3535
});
3636

3737
return {
38+
get window() {
39+
return window;
40+
},
3841
get credentials() {
3942
return credentials;
4043
},

automation/tests/local-basic/settingsKeysImportTests.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { expect, test } from '@playwright/test';
2+
import * as crypto from 'node:crypto';
3+
import * as fs from 'node:fs';
4+
import * as path from 'node:path';
25
import { generateECDSAKeyPair, generateEd25519KeyPair } from '../../utils/crypto/keyUtil.js';
36
import { setupSettingsKeysSuite } from '../helpers/fixtures/settingsKeysSuite.js';
7+
import { fileURLToPath } from 'node:url';
8+
import { clearDialogMockState, setDialogMockState } from '../../utils/runtime/dialogMocks.js';
49

510
test.describe('Settings keys import tests @local-basic', () => {
611
const suite = setupSettingsKeysSuite();
@@ -53,6 +58,56 @@ test.describe('Settings keys import tests @local-basic', () => {
5358
expect(publicKey).toBeTruthy();
5459
});
5560

61+
test('Verify user can import an encrypted private key', async () => {
62+
// Generate private key and encrypt it with a password
63+
const password = 'encrypted-key-password';
64+
const privateKey = crypto
65+
.createPrivateKey({
66+
key: Buffer.from(generateEd25519KeyPair().privateKey, 'hex'),
67+
type: 'pkcs8',
68+
format: 'der',
69+
})
70+
.export({ type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: password });
71+
72+
// Ensure the data directory exists
73+
const __filename = fileURLToPath(import.meta.url);
74+
const __dirname = path.dirname(__filename);
75+
const dataDirectory = path.resolve(__dirname, '../../data');
76+
if (!fs.existsSync(dataDirectory)) {
77+
fs.mkdirSync(dataDirectory, { recursive: true });
78+
}
79+
80+
// Write the file
81+
const fileName = `encrypted-import-${Date.now()}.pem`;
82+
const filePath = path.resolve(dataDirectory, fileName);
83+
fs.writeFileSync(filePath, privateKey);
84+
85+
console.log(`Encrypted private key file generated at: ${filePath}`);
86+
87+
try {
88+
await suite.settingsPage.clickOnKeysTab();
89+
await suite.settingsPage.clickOnImportButton();
90+
await suite.settingsPage.clickOnEncryptedKeysDropdown();
91+
92+
// Browse opens Electron's native dialog through IPC, not a browser file chooser.
93+
await setDialogMockState(suite.window, { openPaths: [filePath] });
94+
await suite.settingsPage.clickOnBrowseEncryptedKeyButton();
95+
await suite.settingsPage.clickOnImportEncryptedKeyButton();
96+
97+
await suite.settingsPage.clickOnSkipImportRecoveryPhraseButton();
98+
await suite.settingsPage.fillInDecryptKeysPassword(password);
99+
await suite.settingsPage.clickOnDecryptEncryptedKeyButton();
100+
101+
await expect.poll(() => suite.settingsPage.getKeyRowCount()).toBe(2);
102+
await expect
103+
.poll(() => suite.registrationPage.getToastMessage())
104+
.toBe('Keys imported successfully');
105+
} finally {
106+
await clearDialogMockState(suite.window);
107+
fs.rmSync(filePath, { force: true });
108+
}
109+
});
110+
56111
test('Verify user can filter keys by All, Recovery Phrase, and Private Key', async () => {
57112
await suite.settingsPage.clickOnKeysTab();
58113

front-end/src/renderer/components/KeyPair/ImportEncrypted/components/DecryptKeyModal.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ watch(
275275
:disabled="decryptPassword.trim().length === 0 || decrypting"
276276
:loading="decrypting"
277277
loading-text="Decrypting..."
278+
data-testid="button-decrypt-encrypted-keys"
278279
>Decrypt</AppButton
279280
>
280281
</div>

front-end/src/renderer/components/KeyPair/ImportEncrypted/components/SelectEncryptedKeysModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ watch(
133133
:disable-on-loading="false"
134134
loading-text="Abort Search"
135135
@click="handleSelect"
136-
data-testid="button-encrypted-keys-folder-import"
136+
data-testid="button-browse-encrypted-keys"
137137
>Browse</AppButton
138138
>
139139
<AppButton

front-end/src/renderer/components/modals/RecoveryPhraseModal.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,13 @@ const handleClearWords = (value: boolean) => {
8585
>Clear</AppButton
8686
>
8787
<div class="flex-between-centered gap-4">
88-
<AppButton color="secondary" type="button" @click="handleSkip">Skip</AppButton>
88+
<AppButton
89+
color="secondary"
90+
type="button"
91+
@click="handleSkip"
92+
data-testid="button-skip-import-recovery-phrase"
93+
>Skip</AppButton
94+
>
8995
<AppButton color="primary" type="submit" :disabled="!user.recoveryPhrase"
9096
>Import</AppButton
9197
>

0 commit comments

Comments
 (0)