Skip to content
Open
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
37 changes: 37 additions & 0 deletions @shared/api/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,43 @@ export const recoverAccount = async ({
return { allAccounts, publicKey, activeAccountId, hasPrivateKey, error };
};

export const recoverAccountFromSecretKey = async ({
password,
secretKey,
isOverwritingAccount = false,
}: {
password: string;
secretKey: string;
isOverwritingAccount: boolean;
}): Promise<{
publicKey: string;
activeAccountId: string;
allAccounts: Array<Account>;
hasPrivateKey: boolean;
error: string;
}> => {
let publicKey = "";
let activeAccountId = "";
let allAccounts = [] as Array<Account>;
let hasPrivateKey = false;
let error = "";

try {
({ allAccounts, publicKey, activeAccountId, hasPrivateKey, error } =
await sendMessageToBackground({
activePublicKey: null,
password,
secretKey,
isOverwritingAccount,
type: SERVICE_TYPES.RECOVER_ACCOUNT_FROM_SECRET_KEY,
}));
} catch (e) {
console.error("Failed to recover account from secret key");
}

return { allAccounts, publicKey, activeAccountId, hasPrivateKey, error };
};

export const confirmPassword = async (
password: string,
): Promise<{
Expand Down
8 changes: 8 additions & 0 deletions @shared/api/types/message-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ export interface RecoverAccountMessage extends BaseMessage {
recoverMnemonic: string;
}

export interface RecoverAccountFromSecretKeyMessage extends BaseMessage {
type: SERVICE_TYPES.RECOVER_ACCOUNT_FROM_SECRET_KEY;
isOverwritingAccount: boolean;
password: string;
secretKey: string;
}

export interface ShowBackupPhraseMessage extends BaseMessage {
type: SERVICE_TYPES.SHOW_BACKUP_PHRASE;
password: string;
Expand Down Expand Up @@ -613,6 +620,7 @@ export type ServiceMessageRequest =
| ConfirmMnemonicPhraseMessage
| ConfirmMigratedMnemonicPhraseMessage
| RecoverAccountMessage
| RecoverAccountFromSecretKeyMessage
| ShowBackupPhraseMessage
| RevealSecretKeyMessage
| ConfirmPasswordMessage
Expand Down
1 change: 1 addition & 0 deletions @shared/constants/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum SERVICE_TYPES {
CONFIRM_MNEMONIC_PHRASE = "CONFIRM_MNEMONIC_PHRASE",
CONFIRM_MIGRATED_MNEMONIC_PHRASE = "CONFIRM_MIGRATED_MNEMONIC_PHRASE",
RECOVER_ACCOUNT = "RECOVER_ACCOUNT",
RECOVER_ACCOUNT_FROM_SECRET_KEY = "RECOVER_ACCOUNT_FROM_SECRET_KEY",
CONFIRM_PASSWORD = "CONFIRM_PASSWORD",
REJECT_ACCESS = "REJECT_ACCESS",
GRANT_ACCESS = "GRANT_ACCESS",
Expand Down
53 changes: 23 additions & 30 deletions extension/e2e-tests/helpers/login-pt.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { expect } from "../test-fixtures";
import { Page } from "@playwright/test";
import { switchNetwork } from "./network";
import { PASSWORD, TEST_ACCOUNT_WORDS } from "./login";
import {
enterImportSecret,
fillPasswordAndConfirm,
waitForOnboardingReady,
} from "./onboarding";

export const PASSWORD = "My-password123";
export { PASSWORD };

export const loginToTestAccountPT = async ({
page,
Expand All @@ -13,40 +19,27 @@ export const loginToTestAccountPT = async ({
}) => {
await page.goto(`chrome-extension://${extensionId}/index.html`);
await page.getByText("Importar carteira").click();

await expect(page.getByText("Criar uma Senha")).toBeVisible();

await page.locator("#new-password-input").fill("My-password123");
await page.locator("#confirm-password-input").fill("My-password123");
await page.locator("#termsOfUse-input").check({ force: true });
await page.getByText("Confirmar").click();
await expect(
page.getByText(
"Importe sua carteira usando uma frase de recuperação ou chave secreta",
),
).toBeVisible();

// GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY
await enterImportSecret({
page,
secret: TEST_ACCOUNT_WORDS.join(" "),
});

const TEST_ACCOUNT_WORDS = [
"card",
"whip",
"erosion",
"fatal",
"reunion",
"foil",
"doctor",
"embark",
"plug",
"note",
"thank",
"company",
];

for (let i = 1; i <= TEST_ACCOUNT_WORDS.length; i++) {
await page.locator(`#MnemonicPhrase-${i}`).fill(TEST_ACCOUNT_WORDS[i - 1]);
}

await page.getByRole("button", { name: "Importar" }).click();
await expect(page.getByText("Tudo pronto!")).toBeVisible({
timeout: 20000,
await expect(page.getByText("Criar uma Senha")).toBeVisible();
await fillPasswordAndConfirm({
page,
password: PASSWORD,
confirmLabel: "Confirmar",
});

await waitForOnboardingReady({ page });

await page.goto(`chrome-extension://${extensionId}/index.html#/`);
await expect(page.getByTestId("account-chip")).toBeVisible({
timeout: 50000,
Expand Down
79 changes: 24 additions & 55 deletions extension/e2e-tests/helpers/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,27 @@ import { Page, BrowserContext } from "@playwright/test";
import { expect } from "../test-fixtures";
import { stubAllExternalApis } from "./stubs";
import { switchNetwork } from "./network";
import { importWallet } from "./onboarding";

const { generateMnemonic } = StellarHDWallet;

export const PASSWORD = "My-password123";

export const TEST_ACCOUNT_WORDS = [
"card",
"whip",
"erosion",
"fatal",
"reunion",
"foil",
"doctor",
"embark",
"plug",
"note",
"thank",
"company",
];

/**
* Creates a new wallet and logs into the extension on Testnet.
*/
Expand All @@ -19,32 +35,11 @@ export const login = async ({
extensionId: string;
}) => {
await page.goto(`chrome-extension://${extensionId}/index.html`);
await page.getByText("Import wallet").click();

await expect(page.getByText("Create a Password")).toBeVisible();

await page.locator("#new-password-input").fill("My-password123");
await page.locator("#confirm-password-input").fill("My-password123");
await page.locator("#termsOfUse-input").check({ force: true });
await page.getByText("Confirm").click();

await expect(
page.getByText("Import wallet from recovery phrase"),
).toBeVisible();
await expect(
page.getByText("You can also paste the phrase in the first box."),
).toBeVisible();

const TEST_WORDS = generateMnemonic({ entropyBits: 128 }).split(" ");

for (let i = 1; i <= TEST_WORDS.length; i++) {
await page.locator(`#MnemonicPhrase-${i}`).fill(TEST_WORDS[i - 1]);
}

await page.getByRole("button", { name: "Import" }).click();

await expect(page.getByText("You’re all set!")).toBeVisible({
timeout: 20000,
await importWallet({
page,
secret: TEST_WORDS.join(" "),
password: PASSWORD,
});

await page.goto(`chrome-extension://${extensionId}/index.html#/`);
Expand Down Expand Up @@ -127,37 +122,11 @@ export const loginToTestAccount = async ({
await stubOverrides();
}
}
await page.getByText("Import wallet").click();

await page.locator("#new-password-input").fill("My-password123");
await page.locator("#confirm-password-input").fill("My-password123");
await page.locator("#termsOfUse-input").check({ force: true });
await page.getByText("Confirm").click();

// GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY

const TEST_ACCOUNT_WORDS = [
"card",
"whip",
"erosion",
"fatal",
"reunion",
"foil",
"doctor",
"embark",
"plug",
"note",
"thank",
"company",
];

for (let i = 1; i <= TEST_ACCOUNT_WORDS.length; i++) {
await page.locator(`#MnemonicPhrase-${i}`).fill(TEST_ACCOUNT_WORDS[i - 1]);
}

await page.getByRole("button", { name: "Import" }).click();
await expect(page.getByText("You’re all set!")).toBeVisible({
timeout: 20000,
await importWallet({
page,
secret: TEST_ACCOUNT_WORDS.join(" "),
password: PASSWORD,
});

await page.goto(`chrome-extension://${extensionId}/index.html#/`);
Expand Down
91 changes: 55 additions & 36 deletions extension/e2e-tests/helpers/onboarding.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,93 @@
import { Page } from "@playwright/test";
import { expect } from "../test-fixtures";

const DEFAULT_IMPORT_TIMEOUT_MS = 10000;
const DEFAULT_IMPORT_TIMEOUT_MS = 20000;

/**
* Starts the import wallet flow by clicking "Import wallet",
* filling in password and confirm password, checking terms of use, and clicking Confirm.
* Waits for the "Import wallet from recovery phrase" screen to be visible.
* Clicks "Import wallet" on the Welcome page and waits for the import text
* box.
*/
export const openImportWallet = async ({ page }: { page: Page }) => {
await page.getByText("Import wallet").click();
await expect(page.getByTestId("import-secret-input")).toBeVisible();
};

/**
* Fills the import text box with a recovery phrase or secret key and clicks
* Next. Does not wait for the password step, so callers can assert an
* input error instead.
*/
export const enterImportSecret = async ({
page,
secret,
}: {
page: Page;
secret: string;
}) => {
await page.getByTestId("import-secret-input").fill(secret);
await page.getByTestId("import-secret-next").click();
};

/**
* Fills the password step (password, confirmation, terms of use) and clicks
* Confirm.
*
* @param confirmPassword - Optional. If not provided, defaults to the same value as password.
* Use a different value to test password mismatch scenarios.
* @param confirmPassword - Optional. Defaults to `password`.
*/
export const startImportWalletFlow = async ({
export const fillPasswordAndConfirm = async ({
page,
password,
confirmPassword,
confirmLabel = "Confirm",
}: {
page: Page;
password: string;
confirmPassword?: string;
confirmLabel?: string;
}) => {
await page.getByText("Import wallet").click();
await expect(page.getByText("Create a password")).toBeVisible();

await expect(page.locator("#new-password-input")).toBeVisible();
await page.locator("#new-password-input").fill(password);
await page
.locator("#confirm-password-input")
.fill(confirmPassword ?? password);
await page.locator("#termsOfUse-input").check({ force: true });
await page.getByText("Confirm").click();

await expect(
page.getByText("Import wallet from recovery phrase"),
).toBeVisible();
await expect(
page.getByText("You can also paste the phrase in the first box."),
).toBeVisible();
await page.getByText(confirmLabel, { exact: true }).click();
};

/**
* Fills in mnemonic phrase inputs with the provided words.
* Words are filled sequentially into inputs named MnemonicPhrase-1, MnemonicPhrase-2, etc.
* Waits for "Your wallet is ready!", the last screen of both onboarding
* flows (create and import). The status attribute is checked instead of the copy so translated
* runs work too.
*/
export const fillMnemonicInputs = async ({
export const waitForOnboardingReady = async ({
page,
words,
timeout = DEFAULT_IMPORT_TIMEOUT_MS,
}: {
page: Page;
words: string[];
timeout?: number;
}) => {
for (let i = 1; i <= words.length; i++) {
const input = page.locator(`input[name="MnemonicPhrase-${i}"]`);
await input.fill(words[i - 1]);
}
await expect(
page.locator('[data-testid="onboarding-status"][data-status="ready"]'),
).toBeVisible({ timeout });
};

/**
* Clicks the Import button and waits for the "You're all set!" message to appear,
* indicating successful wallet import.
*
* @param timeout - Optional. Time in milliseconds to wait for success message.
* Defaults to DEFAULT_IMPORT_TIMEOUT_MS (10000ms).
* Runs the whole import from the Welcome page: text box, Next, password
* step, then waits for the ready screen.
*/
export const clickImportAndWaitForSuccess = async ({
export const importWallet = async ({
page,
timeout = DEFAULT_IMPORT_TIMEOUT_MS,
secret,
password,
timeout,
}: {
page: Page;
secret: string;
password: string;
timeout?: number;
}) => {
await page.getByRole("button", { name: "Import" }).click();
await expect(page.getByText("You’re all set!")).toBeVisible({ timeout });
await openImportWallet({ page });
await enterImportSecret({ page, secret });
await fillPasswordAndConfirm({ page, password });
await waitForOnboardingReady({ page, timeout });
};
Loading
Loading