[ENG-3999] Onboarding part 2 (#184)

* Add storage seedphrase
* add 12 word seedphrase error message check
* add error message check for 24 word seedphrase
* optimize code, add switch 12 to 24 seedphrase test case
* add confirm address copy and save address in file
* [ENG-4031] Implement Sharding for test execution
* Add Lock and login flow
* Added attributes to elements in the homedashboard for better locators
* fix: prefer data-testid over id, and aria-label for options button
* Use static method (#187)
* [ENG-3979] Implement Smoketest execution for PR build
---------

Co-authored-by: DuskaT021 <aleksa2601@gmail.com>
Co-authored-by: Tim Man <tim@secretkeylabs.com>
Co-authored-by: Eduard Bardají Puig <ebardaji@gmail.com>
This commit is contained in:
Christine Pinto
2024-04-15 14:34:01 +02:00
committed by GitHub
parent 708032d15a
commit d2425efbf5
15 changed files with 583 additions and 120 deletions

View File

@@ -9,8 +9,7 @@
"airbnb-typescript",
"airbnb/hooks",
"prettier",
"plugin:@tanstack/eslint-plugin-query/recommended",
"plugin:playwright/recommended"
"plugin:@tanstack/eslint-plugin-query/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
@@ -21,17 +20,10 @@
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": [
"react",
"prettier",
"eslint-plugin-no-inline-styles",
"@tanstack/query",
"eslint-plugin-playwright"
],
"plugins": ["react", "prettier", "eslint-plugin-no-inline-styles", "@tanstack/query"],
"rules": {
"consistent-return": "off",
"no-await-in-loop": "off",
"playwright/valid-title": "off",
"import/prefer-default-export": 1,
"no-restricted-imports": [
"warn",
@@ -62,6 +54,16 @@
"@tanstack/query/exhaustive-deps": 1,
"import/order": 0
},
"overrides": [
{
"files": ["tests/**/*.{js,jsx,ts,tsx}"],
"plugins": ["playwright"],
"extends": ["plugin:playwright/playwright-test"],
"rules": {
"playwright/expect-expect": "off"
}
}
],
"settings": {
"import/resolver": {
"node": {

View File

@@ -41,7 +41,7 @@ jobs:
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
- name: Run UI test suite
run: xvfb-run --auto-servernum --server-args="-screen 0 360x360x24" npx playwright test --reporter=html
run: xvfb-run --auto-servernum --server-args="-screen 0 360x360x24" npx playwright test --grep "#smoketest" --reporter=html
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v3

View File

@@ -1,14 +1,9 @@
name: Playwright Tests
on:
workflow_dispatch:
inputs:
branch:
description: Branch name
required: true
default: develop
jobs:
test:
timeout-minutes: 10
build:
if: ${{ !startsWith(github.head_ref, 'release/') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -24,6 +19,11 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.GH_PACKAGE_REGISTRY_TOKEN }}
run: npm ci
- name: Test
run: |
npx eslint .
npx tsc --noEmit
npm test
- name: Build
env:
TRANSAC_API_KEY: ${{ secrets.TRANSAC_API_KEY }}
@@ -31,14 +31,85 @@ jobs:
MIX_PANEL_TOKEN: ${{ secrets.MIX_PANEL_TOKEN }}
MIX_PANEL_EXPLORE_APP_TOKEN: ${{ secrets.MIX_PANEL_EXPLORE_APP_TOKEN }}
run: npm run build --if-present
- name: Upload Archive
uses: actions/upload-artifact@v3
with:
name: web-extension1
path: ./build
retention-days: 5
if-no-files-found: error
UItest:
needs: [build]
name: UI Test ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }}
timeout-minutes: 10
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5]
shardTotal: [5]
steps:
- uses: actions/checkout@v4
- name: Download build artifact
uses: actions/download-artifact@v3
with:
name: web-extension1
path: ./build
- name: Use Node.js
uses: actions/setup-node@v4
with:
always-auth: true
node-version: 18
registry-url: https://npm.pkg.github.com
scope: '@secretkeylabs'
cache: npm
- name: Install dependencies
env:
NODE_AUTH_TOKEN: ${{ secrets.GH_PACKAGE_REGISTRY_TOKEN }}
run: npm install playwright
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
- name: Run UI test suite
run: xvfb-run --auto-servernum --server-args="-screen 0 360x360x24" npx playwright test --reporter=html
run: xvfb-run --auto-servernum --server-args="-screen 0 360x360x24" npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- name: Upload Playwright report
if: always()
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/
retention-days: 30
name: all-blob-reports
path: blob-report
retention-days: 1
merge-reports:
# Merge reports after playwright-tests, even if some shards have failed
if: ${{ !cancelled() }}
needs: [UItest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
always-auth: true
node-version: 18
registry-url: https://npm.pkg.github.com
scope: '@secretkeylabs'
cache: npm
- name: Install dependencies
env:
NODE_AUTH_TOKEN: ${{ secrets.GH_PACKAGE_REGISTRY_TOKEN }}
run: npm install playwright
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v3
with:
name: all-blob-reports
path: all-blob-reports
- name: Merge into HTML Report
run: npx playwright merge-reports --reporter html ./all-blob-reports
- name: Upload HTML report
uses: actions/upload-artifact@v3
with:
name: html-report--attempt-${{ github.run_attempt }}
path: playwright-report
retention-days: 5

2
.gitignore vendored
View File

@@ -135,3 +135,5 @@ yarn-error.log*
/playwright-report/
/blob-report/
/playwright/.cache/
tests/specs/seedWords.json
tests/specs/addresses.json

View File

@@ -18,10 +18,10 @@ export default defineConfig({
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 1 : 0,
/* Opt out of parallel tests on CI. */
/* Opt out of 2 tests parallel on CI. */
workers: process.env.CI ? 2 : 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [['list'], ['html']],
reporter: process.env.CI ? 'blob' : 'html',
snapshotDir: './playwright-snapshots',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {

View File

@@ -176,7 +176,7 @@ function AccountHeaderComponent({
disabledAccountSelect={disableAccountSwitch}
/>
{!disableMenuOption && (
<OptionsButton onClick={openOptionsDialog}>
<OptionsButton aria-label="Open Header Options" onClick={openOptionsDialog}>
<DotsThreeVertical size={20} fill="white" />
</OptionsButton>
)}

View File

@@ -45,7 +45,7 @@ const AccountInfoContainer = styled.div({
alignItems: 'center',
});
const CurrentAcountContainer = styled.div((props) => ({
const CurrentAccountContainer = styled.div((props) => ({
display: 'flex',
flexDirection: 'column',
paddingLeft: props.theme.space.s,
@@ -331,7 +331,7 @@ function AccountRow({
thirdGradient={gradient[2]}
isBig={isAccountListView}
/>
<CurrentAcountContainer>
<CurrentAccountContainer>
{account && (
<TransparentSpan>
<CurrentAccountTextContainer>
@@ -365,11 +365,11 @@ function AccountRow({
<BarLoader loaderSize={LoaderSize.MEDIUM} />
</BarLoaderContainer>
)}
</CurrentAcountContainer>
</CurrentAccountContainer>
</AccountInfoContainer>
{isAccountListView && (
<OptionsButton onClick={openOptionsDialog}>
<OptionsButton aria-label="Open Account Options" onClick={openOptionsDialog}>
<DotsThreeVertical size={20} fill="white" />
</OptionsButton>
)}

View File

@@ -144,7 +144,7 @@ function BalanceCard(props: BalanceCardProps) {
prefix={`${currencySymbolMap[fiatCurrency]}`}
thousandSeparator
renderText={(value: string) => (
<BalanceAmountText id="balance">{value}</BalanceAmountText>
<BalanceAmountText data-testid="total-balance-value">{value}</BalanceAmountText>
)}
/>
{isRefetching && (

View File

@@ -513,7 +513,7 @@ function Home() {
refetchingRunesData
}
/>
<RowButtonContainer>
<RowButtonContainer data-testid="transaction-buttons-row">
<SquareButton
icon={<ArrowUp weight="regular" size="20" />}
text={t('SEND')}

View File

@@ -9,9 +9,10 @@ export const test = baseTest.extend<{
}>({
// parts of the setup for the persistent context from https://playwright.dev/docs/chrome-extensions#testing
context: async ({}, use) => {
const extPath = path.join(__dirname, '../../build');
const extPath = process.env.BUILD_EXTENSION_PATH || path.join(__dirname, '../../build');
const context = await chromium.launchPersistentContext('', {
args: [`--disable-extensions-except=${extPath}`, `--load-extension=${extPath}`],
// slowMo: 400, // Slows down Playwright operations by 400 milliseconds for showcasing or testing reasons
});
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
await use(context);

View File

@@ -1,4 +1,6 @@
import { expect, type Locator, type Page } from '@playwright/test';
import crypto from 'crypto';
import Landing from './landing';
export default class Onboarding {
readonly linkTOS: Locator;
@@ -25,6 +27,8 @@ export default class Onboarding {
readonly errorMessage2: Locator;
readonly errorMessageSeedPhrase: Locator;
readonly buttonContinue: Locator;
readonly labelSecurityLevelWeak: Locator;
@@ -47,12 +51,27 @@ export default class Onboarding {
readonly instruction: Locator;
readonly headingWalletRestored: Locator;
readonly buttonCloseTab: Locator;
readonly imageSuccess: Locator;
readonly headingRestoreWallet: Locator;
readonly button24SeedPhrase: Locator;
readonly button12SeedPhrase: Locator;
readonly inputSeedPhraseWord: Locator;
readonly inputSeedPhraseWordDisabled: Locator;
readonly buttonUnlock: Locator;
constructor(readonly page: Page) {
this.page = page;
this.buttonContinue = page.getByRole('button', { name: 'Continue' });
this.buttonBack = page.getByRole('button', { name: 'Back' });
this.buttonBackupNow = page.getByRole('button', { name: 'Backup now' });
@@ -70,6 +89,7 @@ export default class Onboarding {
this.inputPassword = page.locator('input[type="password"]');
this.errorMessage = page.getByRole('heading', { name: 'Your password should be at' });
this.errorMessage2 = page.getByRole('heading', { name: 'Please make sure your' });
this.errorMessageSeedPhrase = page.locator('p').filter({ hasText: 'Invalid seed phrase' });
this.labelSecurityLevelWeak = page.locator('p').filter({ hasText: 'Weak' });
this.labelSecurityLevelMedium = page.locator('p').filter({ hasText: 'Medium' });
this.labelSecurityLevelStrong = page.locator('p').filter({ hasText: 'Strong' });
@@ -78,11 +98,16 @@ export default class Onboarding {
this.buttonAccept = page.getByRole('button', { name: 'Accept' });
this.imageSuccess = page.locator('img[alt="success"]');
this.instruction = page.getByRole('heading', { name: 'Locate Xverse' });
this.headingWalletRestored = page.getByRole('heading', { name: 'Wallet restored' });
this.buttonCloseTab = page.getByRole('button', { name: 'Close this tab' });
this.headingRestoreWallet = page.getByRole('heading', { name: 'restore your wallet' });
this.button24SeedPhrase = page.getByRole('button', { name: '24 words' });
this.button12SeedPhrase = page.getByRole('button', { name: '12 words' });
this.inputSeedPhraseWord = page.locator('input');
this.inputSeedPhraseWordDisabled = page.locator('input[type="password"][disabled]');
this.buttonUnlock = page.getByRole('button', { name: 'Unlock' });
}
// TODO add function here for the steps of the onboarding for the case a created wallet should always be created via the UI and is then needed for all following test suits
// id starts from 0
inputWord = (id: number) => this.page.locator(`#input${id}`);
@@ -93,6 +118,7 @@ export default class Onboarding {
}
async checkLegalPage(context) {
await expect(this.page.url()).toContain('legal');
// TODO: Selector outsource
await expect(this.page.locator('div > h1:first-child')).toHaveText(/Legal/);
// check that the links contain href values
@@ -118,6 +144,14 @@ export default class Onboarding {
await newPage.close();
}
async navigateToBackupPage() {
const landingpage = new Landing(this.page);
await landingpage.buttonCreateWallet.click();
await expect(this.page.url()).toContain('legal');
await this.buttonAccept.click();
await expect(this.page.url()).toContain('backup');
}
async checkBackupPage() {
await expect(this.buttonBackupNow).toBeVisible();
await expect(this.buttonBackupLater).toBeVisible();
@@ -126,7 +160,25 @@ export default class Onboarding {
await expect(this.subTitleBackupOnboarding).toBeVisible();
}
// Check the viuals on the first password page before inputting any values in the input field
async navigateToRestorePage() {
const landingpage = new Landing(this.page);
await expect(landingpage.buttonRestoreWallet).toBeVisible();
await landingpage.buttonRestoreWallet.click();
await expect(this.page.url()).toContain('legal');
await this.buttonAccept.click();
await expect(this.page.url()).toContain('restore');
await this.checkRestoreWalletSeedPhrasePage();
}
async checkRestoreWalletSeedPhrasePage() {
await expect(this.buttonContinue).toBeDisabled();
await expect(this.headingRestoreWallet).toBeVisible();
await expect(this.button24SeedPhrase).toBeVisible();
await expect(this.inputSeedPhraseWordDisabled).toHaveCount(12);
await expect(this.inputSeedPhraseWord).toHaveCount(24);
}
// Check the viuals on the first password page before inputing any values in the input field
async checkPasswordPage() {
await expect(this.buttonBack).toBeVisible();
await expect(this.inputPassword).toBeVisible();
@@ -138,6 +190,23 @@ export default class Onboarding {
await expect(this.labelSecurityLevelStrong).toBeHidden();
}
static async multipleClickCheck(button: Locator) {
await button.click();
await button.click();
await button.click();
}
async createWalletSkipBackup(password) {
await this.navigateToBackupPage();
await this.buttonBackupLater.click();
await expect(this.page.url()).toContain('create-password');
await this.inputPassword.fill(password);
await this.buttonContinue.click();
await this.inputPassword.fill(password);
await this.buttonContinue.click();
await expect(this.imageSuccess).toBeVisible();
}
async testPasswordInput({ password, expectations }) {
// Fill in the password input field with the specified password.
await this.inputPassword.fill(password);
@@ -183,4 +252,19 @@ export default class Onboarding {
// Clear the password input field after all checks are done.
await this.inputPassword.clear();
}
static generateSecurePasswordCrypto() {
const length = 9;
const charset =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;':,.<>/?`~";
let password = '';
while (password.length < length) {
// Generate a random byte
const randomValue = crypto.randomInt(charset.length);
password += charset[randomValue];
}
return password;
}
}

68
tests/pages/startPage.ts Normal file
View File

@@ -0,0 +1,68 @@
import { expect, type Locator, type Page } from '@playwright/test';
export default class StartPage {
readonly balance: Locator;
readonly allupperButtons: Locator;
readonly manageTokenButton: Locator;
readonly buttonMenu: Locator;
readonly buttonLock: Locator;
readonly buttonResetWallet: Locator;
readonly buttonDenyDataCollection: Locator;
readonly buttonCopyBitcoinAddress: Locator;
readonly buttonCopyOrdinalsAddress: Locator;
readonly buttonCopyStacksAddress: Locator;
readonly buttonConfirmCopyAddress: Locator;
constructor(readonly page: Page) {
this.page = page;
this.balance = page.getByTestId('total-balance-value');
this.allupperButtons = page.getByTestId('transaction-buttons-row').getByRole('button');
this.manageTokenButton = page.getByRole('button', { name: 'Manage token list' });
this.buttonMenu = page.getByRole('button', { name: 'Open Header Options' });
this.buttonLock = page.getByRole('button', { name: 'Lock' });
this.buttonResetWallet = page.getByRole('button', { name: 'Reset Wallet' });
this.buttonDenyDataCollection = page.getByRole('button', { name: 'Deny' });
this.buttonCopyBitcoinAddress = page.locator('#copy-address-Bitcoin');
this.buttonCopyOrdinalsAddress = page.locator(
'#copy-address-Ordinals\\,\\ BRC-20\\ \\&\\ Runes',
);
this.buttonCopyStacksAddress = page.locator(
'#copy-address-Stacks\\ NFTs\\ \\&\\ SIP-10\\ tokens',
);
this.buttonConfirmCopyAddress = page.getByRole('button', { name: 'I understand' });
}
async checkVisuals() {
// Deny data collection --> modal window is not always appearing so when it does we deny the data collection
if (await this.buttonDenyDataCollection.isVisible()) {
await this.buttonDenyDataCollection.click();
}
// Check if specific visual elements are loaded
await expect(this.balance).toBeVisible();
await expect(this.manageTokenButton).toBeVisible();
await expect(this.buttonMenu).toBeVisible();
// Check if all 4 buttons (send, receive, swap, buy) are visible
await expect(this.allupperButtons).toHaveCount(4);
}
async getAddress(button) {
await expect(button).toBeVisible();
await button.click();
await expect(this.buttonConfirmCopyAddress).toBeVisible();
await this.buttonConfirmCopyAddress.click();
const address = await this.page.evaluate('navigator.clipboard.readText()');
return address;
}
}

View File

@@ -0,0 +1,195 @@
import { expect, test } from '../fixtures/base';
import Landing from '../pages/landing';
import Onboarding from '../pages/onboarding';
import StartPage from '../pages/startPage';
test.beforeEach(async ({ page, extensionId, context }) => {
await page.goto(`chrome-extension://${extensionId}/options.html#/landing`);
// TODO: this fixes a temporary issue with two tabs at the start see technical debt https://linear.app/xverseapp/issue/ENG-3992/two-tabs-open-instead-of-one-since-version-0323-for-start-extension
const pages = await context.pages();
if (pages.length === 2) {
await pages[1].close(); // pages[1] is the second (newest) page
}
});
test.afterEach(async ({ context }) => {
if (context.pages().length > 0) {
// Close the context only if there are open pages
await context.close();
}
});
const strongPW = Onboarding.generateSecurePasswordCrypto();
const fs = require('fs');
const path = require('path');
// Specify the file path for Addresses and Seedphrase
const filePathSeedWords = path.join(__dirname, 'seedWords.json');
const filePathAddresses = path.join(__dirname, 'addresses.json');
test.describe('Create and Restore Wallet Flow', () => {
test('create and restore a wallet via Menu', async ({ page, extensionId, context }) => {
const onboardingpage = new Onboarding(page);
const startpage = new StartPage(page);
await test.step('backup seedphrase and successfully create a wallet', async () => {
await onboardingpage.navigateToBackupPage();
await onboardingpage.buttonBackupNow.click();
await expect(page.url()).toContain('backupWalletSteps');
await expect(onboardingpage.buttonContinue).toBeDisabled();
await expect(onboardingpage.buttonShowSeed).toBeVisible();
await expect(onboardingpage.firstParagraphBackupStep).toBeVisible();
await onboardingpage.buttonShowSeed.click();
await expect(onboardingpage.buttonContinue).toBeEnabled();
const seedWords = await onboardingpage.textSeedWords.allTextContents();
await onboardingpage.buttonContinue.click();
// check if 12 words are displayed
await expect(onboardingpage.buttonSeedWords).toHaveCount(12);
await expect(onboardingpage.secondParagraphBackupStep).toBeVisible();
let seedword = await onboardingpage.selectSeedWord(seedWords);
// Save the seedwords into a file to read it out later to restore
fs.writeFileSync(filePathSeedWords, JSON.stringify(seedWords), 'utf8');
// get all displayed values and filter the value from the actual seedphrase out to do an error message check
const buttonValues = await onboardingpage.buttonSeedWords.evaluateAll((buttons) =>
buttons.map((button) => {
// Assert that the button is an HTMLButtonElement to access the `value` property
if (button instanceof HTMLButtonElement) {
return button.value;
}
return 'testvalue';
}),
);
const filteredValues = buttonValues.filter((value) => value !== seedword);
const randomValue = filteredValues[Math.floor(Math.random() * filteredValues.length)];
await page.locator(`button[value="${randomValue}"]`).click();
// Check if error message is displayed when clicking the wrong seedword
await expect(page.locator('p:has-text("This word is not")')).toBeVisible();
await page.locator(`button[value="${seedword}"]`).click();
seedword = await onboardingpage.selectSeedWord(seedWords);
await page.locator(`button[value="${seedword}"]`).click();
seedword = await onboardingpage.selectSeedWord(seedWords);
await page.locator(`button[value="${seedword}"]`).click();
await onboardingpage.inputPassword.fill(strongPW);
await onboardingpage.buttonContinue.click();
await onboardingpage.inputPassword.fill(strongPW);
await onboardingpage.buttonContinue.click();
await expect(onboardingpage.imageSuccess).toBeVisible();
await expect(onboardingpage.instruction).toBeVisible();
await expect(onboardingpage.buttonCloseTab).toBeVisible();
// Open the startpage directly via URL
await page.goto(`chrome-extension://${extensionId}/popup.html`);
const startPage = new StartPage(page);
await startPage.checkVisuals();
const balanceText = await startPage.balance.innerText();
await expect(balanceText).toBe('$0.00');
// TODO: find better selector for the receive button
await startPage.allupperButtons.nth(1).click();
// Get the addresses and save it in variables
const addressBitcoin = await startPage.getAddress(startPage.buttonCopyBitcoinAddress);
const addressOrdinals = await startPage.getAddress(startPage.buttonCopyOrdinalsAddress);
// Stack Address doesn't have the confirm message
await expect(startPage.buttonCopyStacksAddress).toBeVisible();
await startPage.buttonCopyStacksAddress.click();
const addressStack = await page.evaluate('navigator.clipboard.readText()');
// Reload the page to close the modal window for the addresses as the X button needs to have a better locator
await page.reload();
// click close for the modal window
// TODO find better locator for close button --> issue https://linear.app/xverseapp/issue/ENG-4039/adjust-id-or-add-titles-for-copy-address-button-for-receive-menu
// await expect(page.locator('button.sc-hceviv > svg')).toBeVisible();
// await page.locator('button.sc-hceviv > svg').click();
// Save the Address in a file so that other tests can access them
const dataAddress = JSON.stringify({
addressBitcoin,
addressOrdinals,
addressStack,
});
// Write the file
fs.writeFileSync(filePathAddresses, dataAddress, 'utf8');
});
await test.step('reset Wallet via Menu', async () => {
await expect(startpage.buttonMenu).toBeVisible();
await startpage.buttonMenu.click();
await expect(startpage.buttonResetWallet).toBeVisible();
await startpage.buttonResetWallet.click();
await startpage.buttonResetWallet.click();
await expect(onboardingpage.inputPassword).toBeVisible();
await onboardingpage.inputPassword.fill(strongPW);
await onboardingpage.buttonContinue.click();
});
await test.step('Restore wallet with 12 word seed phrase', async () => {
const landingpage = new Landing(page);
await expect(landingpage.buttonRestoreWallet).toBeVisible();
await landingpage.buttonRestoreWallet.click();
// Clicking on restore opens in this setup a new page for legal
const newPage = await context.pages()[context.pages().length - 1];
await expect(newPage.url()).toContain('legal');
// old page needs to be closed as the test is continuing in the new tab
const pages = await context.pages();
await pages[0].close(); // pages[0] is the first (oldest) page
const onboardingpage2 = new Onboarding(newPage);
await onboardingpage2.buttonAccept.click();
await expect(newPage.url()).toContain('restore');
await onboardingpage2.checkRestoreWalletSeedPhrasePage();
// TODO: There is an bug that the page is refreshed after clicking on any button: https://linear.app/xverseapp/issue/ENG-4028/restore-wallet-reload-page-instead-of-showing-error-message
await onboardingpage2.button24SeedPhrase.click();
await onboardingpage2.checkRestoreWalletSeedPhrasePage();
const seedWords = JSON.parse(fs.readFileSync(filePathSeedWords, 'utf8'));
for (let i = 0; i < seedWords.length; i++) {
await onboardingpage2.inputWord(i).fill(seedWords[i]);
}
await expect(onboardingpage2.buttonContinue).toBeEnabled();
await onboardingpage2.buttonContinue.click();
await onboardingpage2.inputPassword.fill(strongPW);
await onboardingpage2.buttonContinue.click();
await onboardingpage2.inputPassword.fill(strongPW);
await onboardingpage2.buttonContinue.click();
await expect(onboardingpage2.imageSuccess).toBeVisible();
await expect(onboardingpage2.headingWalletRestored).toBeVisible();
await expect(onboardingpage2.buttonCloseTab).toBeVisible();
// Open the startpage directly via URL
await newPage.goto(`chrome-extension://${extensionId}/popup.html`);
const startPage = new StartPage(newPage);
await startPage.checkVisuals();
const balanceText = await startPage.balance.innerText();
await expect(balanceText).toBe('$0.00');
await startPage.allupperButtons.nth(1).click();
// Get the Addresses
const addressBitcoinCheck = await startPage.getAddress(startPage.buttonCopyBitcoinAddress);
const addressOrdinalsCheck = await startPage.getAddress(startPage.buttonCopyOrdinalsAddress);
// Stack Address doesn't have the confirm message
await expect(startPage.buttonCopyStacksAddress).toBeVisible();
await startPage.buttonCopyStacksAddress.click();
const addressStackCheck = await newPage.evaluate('navigator.clipboard.readText()');
// Read and parse the file
const rawData = fs.readFileSync(filePathAddresses, 'utf8');
const { addressBitcoin, addressOrdinals, addressStack } = JSON.parse(rawData);
// Check if the Addresses are the same as from the file
await expect(addressBitcoin).toBe(addressBitcoinCheck);
await expect(addressOrdinals).toBe(addressOrdinalsCheck);
await expect(addressStack).toBe(addressStackCheck);
});
});
});

View File

@@ -6,7 +6,7 @@ test.describe('healthcheck', () => {
await context.close();
});
test.skip('healthcheck', async ({ page, extensionId }) => {
test('healthcheck #smoketest', async ({ page, extensionId }) => {
await page.goto(`chrome-extension://${extensionId}/options.html#/landing`);
const landingpage = new Landing(page);
await landingpage.initialize();

View File

@@ -1,10 +1,10 @@
import * as bip39 from 'bip39';
import { expect, test } from '../fixtures/base';
import { passwordTestCases } from '../fixtures/passwordTestData';
import Landing from '../pages/landing';
import Onboarding from '../pages/onboarding';
import StartPage from '../pages/startPage';
// TODO outsoure Password value
const strongPW = 'Admin12345567!!!!';
const strongPW = Onboarding.generateSecurePasswordCrypto();
test.describe('onboarding flow', () => {
test.beforeEach(async ({ page, extensionId, context }) => {
@@ -22,38 +22,25 @@ test.describe('onboarding flow', () => {
}
});
test('visual check legal page', async ({ page, context }) => {
const landingpage = new Landing(page);
// Click on create Wallet and check legal page elements
await landingpage.buttonCreateWallet.click();
test('visual check legal page', async ({ page, context, extensionId }) => {
const onboardingpage = new Onboarding(page);
await expect(page.url()).toContain('legal');
// Skip Landing and go directly to legal via URL
await page.goto(`chrome-extension://${extensionId}/options.html#/legal`);
await onboardingpage.checkLegalPage(context);
});
// Visual check of the first page for backup
test('visual check backup page main', async ({ page }) => {
const landingpage = new Landing(page);
await landingpage.buttonCreateWallet.click();
test('visual check backup page main #smoketest', async ({ page }) => {
const onboardingpage = new Onboarding(page);
await expect(page.url()).toContain('legal');
await onboardingpage.buttonAccept.click();
await expect(page.url()).toContain('backup');
await onboardingpage.navigateToBackupPage();
await onboardingpage.checkBackupPage();
});
// Visual check of the first page for password creation
test('skip backup and visual check password page', async ({ page }) => {
const landingpage = new Landing(page);
await landingpage.buttonCreateWallet.click();
test('skip backup and visual check password page', async ({ page, extensionId }) => {
const onboardingpage = new Onboarding(page);
await expect(page.url()).toContain('legal');
await onboardingpage.buttonAccept.click();
await expect(page.url()).toContain('backup');
await onboardingpage.buttonBackupLater.click();
// Skip landing and go directly to create password via URL
await page.goto(`chrome-extension://${extensionId}/options.html#/create-password`);
await expect(page.url()).toContain('create-password');
await onboardingpage.checkPasswordPage();
await onboardingpage.buttonBack.click();
@@ -61,14 +48,9 @@ test.describe('onboarding flow', () => {
});
// No Wallet is created in this step as we only check the display of the error messages and that you can't create a wallet if passwords don't align
test('Skip backup and check password error messages', async ({ page }) => {
const landingpage = new Landing(page);
await landingpage.buttonCreateWallet.click();
test('Skip backup and check password error messages #smoketest', async ({ page }) => {
const onboardingpage = new Onboarding(page);
await expect(page.url()).toContain('legal');
await onboardingpage.buttonAccept.click();
await expect(page.url()).toContain('backup');
await onboardingpage.navigateToBackupPage();
await onboardingpage.buttonBackupLater.click();
await expect(page.url()).toContain('create-password');
@@ -89,9 +71,7 @@ test.describe('onboarding flow', () => {
await onboardingpage.buttonContinue.click();
await expect(onboardingpage.errorMessage2).toBeVisible();
// multiple times clicking on continue to check that the user stays on the page and can't continue even of clicked multiple times
await onboardingpage.buttonContinue.click();
await onboardingpage.buttonContinue.click();
await onboardingpage.buttonContinue.click();
await Onboarding.multipleClickCheck(onboardingpage.buttonContinue);
await expect(onboardingpage.errorMessage2).toBeVisible();
await onboardingpage.buttonBack.click();
await expect(onboardingpage.inputPassword).toHaveValue(/.+/);
@@ -100,65 +80,125 @@ test.describe('onboarding flow', () => {
await expect(onboardingpage.errorMessage2).toBeVisible();
});
test('backup seedphrase and successfully create a wallet', async ({ page, context }) => {
const landingpage = new Landing(page);
await landingpage.buttonCreateWallet.click();
test('Restore wallet error message check for false 12 word seed phrase #smoketest', async ({
page,
}) => {
const onboardingpage = new Onboarding(page);
await expect(page.url()).toContain('legal');
await onboardingpage.buttonAccept.click();
await expect(page.url()).toContain('backup');
await onboardingpage.buttonBackupNow.click();
await expect(page.url()).toContain('backupWalletSteps');
await expect(onboardingpage.buttonContinue).toBeDisabled();
await expect(onboardingpage.buttonShowSeed).toBeVisible();
await expect(onboardingpage.firstParagraphBackupStep).toBeVisible();
await onboardingpage.buttonShowSeed.click();
await onboardingpage.navigateToRestorePage();
// TODO: There is an bug that the page is refreshed after clicking on any button: https://linear.app/xverseapp/issue/ENG-4028/restore-wallet-reload-page-instead-of-showing-error-message
await onboardingpage.button24SeedPhrase.click();
await onboardingpage.checkRestoreWalletSeedPhrasePage();
// get 12 words from bip39
const mnemonic = bip39.generateMnemonic();
const wordsArray = mnemonic.split(' '); // Split the mnemonic by spaces
// We only input 11 word to cause the error message
for (let i = 0; i < wordsArray.length - 1; i++) {
await onboardingpage.inputWord(i).fill(wordsArray[i]);
}
await expect(onboardingpage.buttonContinue).toBeEnabled();
await onboardingpage.buttonContinue.click();
await expect(onboardingpage.inputSeedPhraseWordDisabled).toHaveCount(12);
await expect(onboardingpage.buttonContinue).toBeEnabled();
await expect(onboardingpage.errorMessageSeedPhrase).toBeVisible();
// multiple times clicking on continue to check that the user stays on the page and can't continue even of clicked multiple times
await Onboarding.multipleClickCheck(onboardingpage.buttonContinue);
await expect(page.url()).toContain('restoreWallet');
await expect(onboardingpage.inputSeedPhraseWordDisabled).toHaveCount(12);
await expect(onboardingpage.errorMessageSeedPhrase).toBeVisible();
// empty all fields and check if continue button is disabled
for (let i = 0; i < 12; i++) {
await onboardingpage.inputWord(i).clear();
}
await expect(onboardingpage.buttonContinue).toBeDisabled();
await expect(onboardingpage.inputSeedPhraseWordDisabled).toHaveCount(12);
await expect(onboardingpage.errorMessageSeedPhrase).toBeHidden();
});
test('Restore wallet Error Message check for false 24 word seed phrase', async ({ page }) => {
const onboardingpage = new Onboarding(page);
await onboardingpage.navigateToRestorePage();
await onboardingpage.button24SeedPhrase.click();
// TODO: There is an bug that the page is refreshed after clicking on any button https://linear.app/xverseapp/issue/ENG-4028/restore-wallet-reload-page-instead-of-showing-error-message
await onboardingpage.checkRestoreWalletSeedPhrasePage();
await onboardingpage.button24SeedPhrase.click();
// All input fields should now be visible and enabled
await expect(onboardingpage.inputSeedPhraseWordDisabled).toHaveCount(0);
await expect(onboardingpage.inputSeedPhraseWord).toHaveCount(24);
// get 24 words from bip39
const mnemonic = bip39.generateMnemonic(256);
const wordsArray = mnemonic.split(' '); // Split the mnemonic by spaces
for (let i = 0; i < wordsArray.length - 1; i++) {
await onboardingpage.inputWord(i).fill(wordsArray[i]);
}
await expect(onboardingpage.buttonContinue).toBeEnabled();
const seedWords = await onboardingpage.textSeedWords.allTextContents();
await onboardingpage.buttonContinue.click();
// check if 12 words are displayed
await expect(onboardingpage.buttonSeedWords).toHaveCount(12);
await expect(onboardingpage.secondParagraphBackupStep).toBeVisible();
let seedword = await onboardingpage.selectSeedWord(seedWords);
// As the seed phrase is not complete an error should be shown and the continue button is still enabled
await expect(onboardingpage.buttonContinue).toBeEnabled();
await expect(onboardingpage.inputSeedPhraseWordDisabled).toHaveCount(0);
await expect(onboardingpage.errorMessageSeedPhrase).toBeVisible();
// get all displayed values and filter the value from the actual seedphrase out to do an error message check
const buttonValues = await onboardingpage.buttonSeedWords.evaluateAll((buttons) =>
buttons.map((button) => {
// Assert that the button is an HTMLButtonElement to access the `value` property
if (button instanceof HTMLButtonElement) {
return button.value;
}
return 'testvalue';
}),
);
// multiple times clicking on continue to check that the user stays on the page and can't continue even of clicked multiple times
await Onboarding.multipleClickCheck(onboardingpage.buttonContinue);
await expect(page.url()).toContain('restoreWallet');
const filteredValues = buttonValues.filter((value) => value !== seedword);
const randomValue = filteredValues[Math.floor(Math.random() * filteredValues.length)];
await page.locator(`button[value="${randomValue}"]`).click();
await expect(onboardingpage.inputSeedPhraseWordDisabled).toHaveCount(0);
await expect(onboardingpage.errorMessageSeedPhrase).toBeVisible();
// Check if error message is displayed when clicking the wrong seedword
await expect(page.locator('p:has-text("This word is not")')).toBeVisible();
// empty all fields and check if continue button is disabled
for (let i = 0; i < 24; i++) {
await onboardingpage.inputWord(i).clear();
}
await expect(onboardingpage.buttonContinue).toBeDisabled();
await expect(onboardingpage.errorMessageSeedPhrase).toBeHidden();
});
await page.locator(`button[value="${seedword}"]`).click();
seedword = await onboardingpage.selectSeedWord(seedWords);
await page.locator(`button[value="${seedword}"]`).click();
seedword = await onboardingpage.selectSeedWord(seedWords);
await page.locator(`button[value="${seedword}"]`).click();
test('Restore wallet check switch 12 to 24 seed phrase', async ({ page, extensionId }) => {
const onboardingpage = new Onboarding(page);
// TODO: currently the error messages as not shown for the passwords in this step so this check needs to be commment out until it is fixed
/* for (const testCase of passwordTestCases) {
await onboardingpage.testPasswordInput(testCase);
} */
// Skip Landing and go directly to restore wallet via URL
await page.goto(`chrome-extension://${extensionId}/options.html#/restoreWallet`);
await onboardingpage.button24SeedPhrase.click();
// TODO: There is an bug that the page is refreshed after clicking on any button https://linear.app/xverseapp/issue/ENG-4028/restore-wallet-reload-page-instead-of-showing-error-message
await onboardingpage.checkRestoreWalletSeedPhrasePage();
await onboardingpage.button24SeedPhrase.click();
await expect(onboardingpage.inputSeedPhraseWordDisabled).toHaveCount(0);
await expect(onboardingpage.inputSeedPhraseWord).toHaveCount(24);
await expect(onboardingpage.buttonContinue).toBeDisabled();
await expect(onboardingpage.button12SeedPhrase).toBeVisible();
await onboardingpage.button12SeedPhrase.click();
await expect(onboardingpage.inputSeedPhraseWordDisabled).toHaveCount(12);
await expect(onboardingpage.inputSeedPhraseWord).toHaveCount(24);
await expect(onboardingpage.buttonContinue).toBeDisabled();
await expect(onboardingpage.button24SeedPhrase).toBeVisible();
});
test('Lock and login #smoketest', async ({ page, extensionId }) => {
const onboardingpage = new Onboarding(page);
const startpage = new StartPage(page);
await onboardingpage.createWalletSkipBackup(strongPW);
await page.goto(`chrome-extension://${extensionId}/popup.html`);
await expect(startpage.buttonMenu).toBeVisible();
await startpage.buttonMenu.click();
await expect(startpage.buttonLock).toBeVisible();
await startpage.buttonLock.click();
await expect(onboardingpage.inputPassword).toBeVisible();
await onboardingpage.inputPassword.fill(strongPW);
await onboardingpage.buttonContinue.click();
await onboardingpage.inputPassword.fill(strongPW);
await onboardingpage.buttonContinue.click();
await expect(onboardingpage.imageSuccess).toBeVisible();
await expect(onboardingpage.instruction).toBeVisible();
await expect(onboardingpage.buttonCloseTab).toBeVisible();
await onboardingpage.buttonUnlock.click();
await startpage.checkVisuals();
});
});