Compare commits

...

6 Commits

Author SHA1 Message Date
tommy a94272ffd6 update branch to be in line with develop 2022-08-18 13:28:05 +02:00
Benedetta 02da10e222 clean up 2022-08-04 15:37:05 +02:00
Benedetta 2637924e9c Fixing some to-do bits 2022-07-27 08:51:16 +02:00
Benedetta bbaa06417d Adding more tests 2022-07-25 08:39:07 +02:00
Benedetta edcaf72714 Creating the branch and initial work 2022-07-19 14:22:31 +02:00
Tommy 80ad7c1798 implement base changes for updating wallet-tests to use typescript 2022-07-14 17:51:48 +02:00
83 changed files with 3086 additions and 4277 deletions
+5 -5
View File
@@ -51,12 +51,12 @@ jobs:
- name: Install dependencies
run: yarn install
working-directory: nym-wallet/webdriver
working-directory: nym-wallet/wallet-ui-tests
- name: Remove existing user datafile
uses: JesseTG/rm@v1.0.2
with:
path: nym-wallet/webdriver/common/data/user-data.json
path: nym-wallet/wallet-ui-tests/common/user-data.json
- name: Create user data json file
id: create-json
@@ -64,7 +64,7 @@ jobs:
with:
name: "user-data.json"
json: ${{ secrets.WALLET_USERDATA }}
dir: "nym-wallet/webdriver/common/data/"
dir: "nym-wallet/wallet-ui-tests/common/"
- name: Install tauri-driver
uses: actions-rs/cargo@v1
@@ -73,5 +73,5 @@ jobs:
args: tauri-driver
- name: Launch tests
run: xvfb-run yarn test:runall
working-directory: nym-wallet/webdriver
run: xvfb-run yarn test
working-directory: nym-wallet/wallet-ui-tests
Generated
+512 -466
View File
File diff suppressed because it is too large Load Diff
@@ -1,13 +0,0 @@
{
"version": 1,
"accounts": [
{
"id": "default",
"account": {
"ciphertext": "cq5w4W5ex5eFFRcqLG+824XyUAUoYmrRY3NGw/rue6/mLoQKQE/07+BxzRuKjyYFasC1HBPg41KJwp2IY+/7+80rB9aXPpaKVLUcG9U40qgCw66WhgxTrXOnrt5toefpSTBL7f9N/PVwpuumfAgD9CS0ioB7/9Qoea7nYKkextGX15ex26B/ndQddvUkQ4gx+Vq7OLymv4l+nkdZ2nKMja349zd/BjnzPBB68/iIjyYlivVjtQ7FRbvpNRj6Mjg4905wGlO7bTpkw+RGiaGK4pK8fTWz8gAKr8GYoXPD",
"salt": "SPVGdbVyoEayD4ZzM4I+Jg==",
"iv": "tjpn/tRjD1gty+fQ"
}
}
]
}
@@ -1,13 +0,0 @@
{
"version": 1,
"accounts": [
{
"id": "default",
"account": {
"ciphertext": "3MDgoU2i5QMc9r80yPeq2AMk5wpkke0tXum5NsOE5NcFciF+aHLQW0dvXbGszap1y3nN4+YZD3cgGrmtKh/cChqRGJDkniaxdf3XPHh9RkiWXw2KSHHeyGrFY0INJeiky1ZtUFhWhopcHJWSnfCmVC15YFpnM5xOKpITjHAFhGt98MaYR+mS+3zoUFrjYbaZRh2TR2lFWsbR8YU1uaTYqJZ1HX1PBCub6aS3vjQm0Fwa+hAtR/gMymXJc5qtruTO4NbqYtMj3Z9eIgoVB+56SLAXlIF1Uo1pjvV0mx1hWNNiIc10ujF/wl/nnKF6icOcmrfm9XhOtsvUYBsE/wAIJZw3LKXgSX+hJbOl+zLAwJZK1xiL8n/nM1IJZDn+Wu6z0OzRaj9S7T16+brMw1oaqjk56saM8n5z725fizJj+ur6gnPBWnoyHPaCHgHdB2PKQNY0ZlwRM6dVncRaEWQDLAboyMq3FXxK9UbusNcDFpYw6bdnuJlNVf6y9yxwyvkUrt5YtgfkLyoW42z1PVtVWsV9P8eE/A/tnYjXf34xvba3K8Y1/3DTi7uuydNrSR/XhA+pevz68VWCbY+j746Yi8Lz7altePphkjfJAezodobKvMplXzqInopIWNovyemw/+1E7WZbkQIOAXg1WC1+Y/df+dffRGuGRdDerfRLmA5XLej1M/wE3WQ7b9KwlAo6XJ4hnQKwyDCqYP/ButBXW1AOnnZpCq59gGbiccZJsTMZB4OP95yFPgz8//IeDgma2PDixVmDEp0SGHhN7dlSoNa5eoglblqzJu/TcTA6jmQFA3ef0GiA3QzBjmyB4bz0bFybh8XA1brVIVlsjRwXb3/UYaVqsP6Hy1QDUpZofXIJs5lK0hUd0ECdaNFXXgHd25ifPocp09WLFyK92H6i3ABDZ7pu3b4lTUt6kHt6LTVsKkyylmYf2iMHnCcmfy4uxGTXxRjPjMgKL8pd++OZ3q62jLBuoTjgdj6pccwDvD+NYQ2FFeHmBzxyTLqUyKltYiyFlJHWLKOcXyeDHzRhHic+e/wn3VhM3NdrvtqYWA9m72Ye1L1I7VX7KatGurG6CeiFiY5xHxxpLT7dF0fJ7uxRye4JnRyYQuU7iK72qCKjgYjwjCIha4qPi5Q/x6S+uVe7yX5Eb73L3eB+IlkyW9wPHmSOcE4GpbMU96tK8xoxT0T9eQlj050GDnJ/oI2XHfZTs1bIxsjfZqW03g==",
"salt": "wXR3RnPmsoA3ncrixIvaUw==",
"iv": "/Zjn1OXsLJhA43n/"
}
}
]
}
@@ -1,21 +0,0 @@
{
"version": 1,
"accounts": [
{
"id": "first",
"account": {
"ciphertext": "icnpxLmr/H7FIIOaEf7DYNLuM6uhh7poEppXpYCllQD33TjY+8eLtVvhEQmjX60IQeFOd+1JCcrHa2B12vlBAYlfM4gBxA6d2ZJ8+Dw/vNvBNyChiyUx2euV3vPGOs22r/XDBsmEeF40XZcXftQZa2kzYaPnkbP+eiMOIWkcY4FYOEHwx5SxT4VBPZIrVTC3iDalJLWybVbbw/Bc2zbzEXI1ckg4Ccydj95SMil9BiyDpALfZqwlai7I97S+BjmcVxSCsYqFjTkRUHVMjrEr7fWHKU4DIOM=",
"salt": "CtnbfkxTybqz0U4cPHW2jQ==",
"iv": "77ZROU6dAMttEWwS"
}
},
{
"id": "second",
"account": {
"ciphertext": "nsqZHdQFlskglc5izKgnr8sBwdMmd82h2Rnjdos9EUca3cqkUdFYEjZDsK8OGR3GZ9alLTNt/1U97Rvvr2HPAWbzl23FW2YXaLTA6yj6ZwQK5w0MYE061NYbcxNHuzT9f5aQWkGULAk4RWb5t8eUX7y/NdJr3tA5xuGOLhooTfBB98/4RpupDsYGZp1DPC/GMFppOA3GmKs9bacZm805Bhfq5mwhXab1SjJQpFHMHisCMhxo/oLqulKML1tQMetBdqDTjJmPpdUnd1mi",
"salt": "J2TMLjKv4dkZ/kXso9FGhg==",
"iv": "CTqqoMa4LetvBKCP"
}
}
]
}
+2 -2
View File
@@ -28,7 +28,7 @@ type ClientAddressProps = {
showEntireAddress?: boolean;
};
export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string }> = ({
export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string } > = ({
withLabel,
withCopy,
showEntireAddress,
@@ -44,7 +44,7 @@ export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string }>
)}
<AddressTooltip address={address} visible={!showEntireAddress}>
<Typography variant="body2" component="span" sx={{ mr: 1, color: 'text.primary', fontWeight: 400 }}>
<Typography data-testid="accountNumber" variant="body2" component="span" sx={{ mr: 1, color: 'text.primary', fontWeight: 400 }}>
{showEntireAddress ? address || '' : splice(6, address)}
</Typography>
</AddressTooltip>
@@ -37,6 +37,8 @@ export const CopyToClipboard = ({ text = '', iconButton }: { text?: string; icon
}}
>
{!copied ? <ContentCopy sx={{ fontSize: 14 }} /> : <Check color="success" sx={{ fontSize: 14 }} />}
{!copied ? <ContentCopy data-testid="copyIcon"
fontSize="small" /> : <Check color="success" />}
</IconButton>
</Tooltip>
);
+2 -1
View File
@@ -18,10 +18,11 @@ export const Mnemonic = ({
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future
</Typography>
</Warning>
<TextField multiline rows={3} value={mnemonic} fullWidth />
<TextField multiline rows={3} value={mnemonic} fullWidth data-testid="mnemonicPhrase"/>
<Button
color="inherit"
data-testid="copyMnemonic"
disableElevation
size="large"
onClick={() => {
@@ -56,7 +56,7 @@ export const ConfirmationModal = ({
const ConfirmButton =
typeof confirmButton === 'string' ? (
<Button onClick={onConfirm} variant="contained" fullWidth disabled={disabled} sx={{ py: 1.6 }}>
<Typography variant="button" fontSize="large">
<Typography variant="button" fontSize="large" data-testid={confirmButton}>
{confirmButton}
</Typography>
</Button>
@@ -16,7 +16,7 @@ const NetworkItem: React.FC<{ title: string; isSelected: boolean; onSelect: () =
isSelected,
onSelect,
}) => (
<ListItem button onClick={onSelect}>
<ListItem button onClick={onSelect} data-testid={title}>
<ListItemIcon>{isSelected && <CheckSharp color="success" />}</ListItemIcon>
<ListItemText>{title}</ListItemText>
</ListItem>
@@ -38,6 +38,7 @@ export const NetworkSelector = () => {
return (
<>
<Button
data-testid="networkEnv"
variant="text"
color="inherit"
sx={{ color: 'text.primary' }}
+1 -1
View File
@@ -31,7 +31,7 @@ export const NymCard: React.FC<{
{noPadding ? (
<CardContentNoPadding>{children}</CardContentNoPadding>
) : (
<CardContent sx={{ p: 3 }}>{children}</CardContent>
<CardContent sx={{ p: 3 }} >{children}</CardContent>
)}
</Card>
);
@@ -62,6 +62,9 @@ export const SendInputModal = ({
fullWidth
onChange={(e) => onAddressChange(e.target.value)}
value={toAddress}
inputProps={{
"data-testid": "recipientAddress",
}}
/>
<CurrencyFormField
placeholder="Amount"
@@ -73,7 +76,7 @@ export const SendInputModal = ({
initialValue={amount?.amount}
denom={denom}
/>
<Typography fontSize="smaller" sx={{ color: 'error.main' }}>
<Typography fontSize="smaller" sx={{ color: 'error.main' }} >
{error}
</Typography>
</Stack>
@@ -31,7 +31,7 @@ export const SendSuccessModal = ({
{txDetails && (
<>
<Typography variant="h5">{txDetails.amount}</Typography>
<Link href={txDetails.txUrl} target="_blank" sx={{ ml: 1 }} text="View on blockchain" />
<Link href={txDetails.txUrl} target="_blank" sx={{ ml: 1 }} text="View on blockchain" data-testid="viewOnBlockchain"/>
</>
)}
</Stack>
+6
View File
@@ -13,6 +13,9 @@ export const MnemonicInput: React.FC<{
<Stack spacing={2}>
<TextField
label="Mnemonic"
inputProps={{
"data-testid": "mnemonicInput",
}}
type={showPassword ? 'input' : 'password'}
value={mnemonic}
onChange={(e) => onUpdateMnemonic(e.target.value)}
@@ -63,6 +66,9 @@ export const PasswordInput: React.FC<{
</IconButton>
),
}}
inputProps={{
"data-testid": label,
}}
/>
</Box>
{error && <Error message={error} />}
@@ -6,7 +6,7 @@ export const Title = ({ title }: { title: string }) => (
);
export const Subtitle = ({ subtitle }: { subtitle: string }) => (
<Typography sx={{ color: 'common.white', textAlign: 'center', maxWidth: 450 }}>{subtitle}</Typography>
<Typography data-testid={subtitle} sx={{ color: 'common.white', textAlign: 'center', maxWidth: 450 }}>{subtitle}</Typography>
);
export const SubtitleSlick = ({ subtitle }: { subtitle: string }) => (
@@ -53,7 +53,8 @@ export const WordTiles = ({
return (
<Grid container spacing={3} justifyContent="center">
{mnemonicWords.map(({ name, index, disabled }) => (
<Grid item xs={2} key={index} onClick={() => onClick?.({ name, index })}>
<Grid
item xs={2} key={index} onClick={() => onClick?.({ name, index })} data-testid="mnemonicWordTile">
<WordTile
mnemonicWord={name}
index={showIndex ? index : undefined}
@@ -79,7 +80,7 @@ const HiddenWord = ({ mnemonicWord }: { mnemonicWord: THiddenMnemonicWord }) =>
</Box>
</Fade>
</Box>
<Typography>{mnemonicWord.index}.</Typography>
<Typography data-testid="wordIndex">{mnemonicWord.index}.</Typography>
</Stack>
);
@@ -20,6 +20,7 @@ export const ConfirmMnemonic = () => {
<Subtitle subtitle="Enter the mnemonic you wish to create a password for" />
<MnemonicInput mnemonic={localMnemonic} onUpdateMnemonic={(mnc) => setLocalMnemonic(mnc)} error={error} />
<Button
data-testid="nextToPasswordCreation"
size="large"
variant="contained"
fullWidth
@@ -37,6 +38,7 @@ export const ConfirmMnemonic = () => {
Next
</Button>
<Button
data-testid="backToMnemonicSignIn"
size="large"
color="inherit"
fullWidth
@@ -57,6 +57,7 @@ export const ConnectPassword = () => {
label="Confirm password"
/>
<Button
data-testid="createPasswordButton"
size="large"
variant="contained"
disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading}
@@ -65,6 +66,7 @@ export const ConnectPassword = () => {
{isLoading ? <CircularProgress size={25} /> : 'Create password'}
</Button>
<Button
data-testid="backToStep1PasswordCreation"
size="large"
color="inherit"
onClick={() => {
@@ -23,6 +23,7 @@ export const CreateMnemonic = () => {
<Button
variant="contained"
data-testid="iSavedMnemonic"
color="primary"
disableElevation
size="large"
@@ -33,6 +34,7 @@ export const CreateMnemonic = () => {
I saved my mnemonic
</Button>
<Button
data-testid="backToWelcome"
onClick={() => {
resetState();
navigate(-1);
@@ -57,13 +57,14 @@ export const CreatePassword = () => {
/>
<Button
size="large"
data-testid="nextStorePassword"
variant="contained"
disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading}
onClick={storePassword}
>
Next
</Button>
<Button size="large" color="info" onClick={handleSkip}>
<Button size="large" color="info" onClick={handleSkip} data-testid="skipPasswordAndSignInWithMnemonic">
Skip and sign in with mnemonic
</Button>
</Stack>
@@ -11,18 +11,18 @@ export const ExistingAccount = () => {
<Title title="Welcome to Nym" />
<SubtitleSlick subtitle="NEXT GENERATION OF PRIVACY" />
<Stack spacing={2} sx={{ width: 300 }}>
<Button variant="contained" size="large" onClick={() => navigate('/sign-in-mnemonic')} fullWidth>
<Button variant="contained" size="large" onClick={() => navigate('/sign-in-mnemonic')} fullWidth data-testid="signInWithMnemonic">
Sign in with mnemonic
</Button>
<Typography sx={{ textAlign: 'center', fontWeight: 600 }}>or</Typography>
<Button variant="contained" size="large" fullWidth onClick={() => navigate('/sign-in-password')}>
<Button variant="contained" size="large" fullWidth onClick={() => navigate('/sign-in-password')} data-testid="signInWithPassword">
Sign in with password
</Button>
<Box display="flex" justifyContent="space-between">
<Button color="inherit" onClick={() => navigate('/')}>
<Button color="inherit" onClick={() => navigate('/')} data-testid="backToWelcomePage">
Back
</Button>
<Button color="info" onClick={() => navigate('/forgot-password')}>
<Button color="info" onClick={() => navigate('/forgot-password')} data-testid="forgotPassword">
Forgot password?
</Button>
</Box>
@@ -39,15 +39,15 @@ export const SignInMnemonic = () => {
>
<Stack spacing={2}>
<MnemonicInput mnemonic={mnemonic} onUpdateMnemonic={(mnc) => setMnemonic(mnc)} error={error} />
<Button variant="contained" size="large" fullWidth type="submit">
<Button variant="contained" size="large" fullWidth type="submit" data-testid="signInSubmitButton">
Sign in with mnemonic
</Button>
<Box display="flex" justifyContent={passwordExists ? 'center' : 'space-between'}>
<Button color="inherit" onClick={() => handlePageChange(-1)}>
<Button color="inherit" onClick={() => handlePageChange(-1)} data-testid="backToSignInOptions">
Back
</Button>
{!passwordExists && (
<Button color="info" onClick={() => handlePageChange('/confirm-mnemonic')}>
<Button color="info" onClick={() => handlePageChange('/confirm-mnemonic')} data-testid="goToCreatePassword">
Create a password
</Button>
)}
@@ -29,6 +29,7 @@ export const SignInPassword = () => {
autoFocus
/>
<Button
data-testid="signInPasswordButton"
variant="contained"
size="large"
fullWidth
@@ -38,6 +39,7 @@ export const SignInPassword = () => {
</Button>
<Box display="flex" justifyContent="space-between">
<Button
data-testid="backToSignInOptionsFromPassword"
color="inherit"
disableElevation
onClick={() => {
@@ -49,6 +51,7 @@ export const SignInPassword = () => {
</Button>
<Button
data-testid="forgotPasswordButton"
color="info"
onClick={() => {
setError(undefined);
@@ -52,6 +52,7 @@ export const VerifyMnemonic = () => {
<Stack spacing={3} sx={{ width: 300 }}>
<Button
variant="contained"
data-testid="nextToStep3"
fullWidth
size="large"
disabled={currentSelection !== numberOfRandomWords}
@@ -59,7 +60,7 @@ export const VerifyMnemonic = () => {
>
Next
</Button>
<Button color="inherit" fullWidth size="large" onClick={() => navigate(-1)}>
<Button color="inherit" fullWidth size="large" onClick={() => navigate(-1)} data-testid="backToStep1">
Back
</Button>
</Stack>
@@ -19,6 +19,7 @@ export const WelcomeContent: React.FC<{}> = () => {
variant="contained"
size="large"
onClick={() => navigate('/existing-account')}
data-testid="signIn"
>
Sign in
</Button>
@@ -29,6 +30,7 @@ export const WelcomeContent: React.FC<{}> = () => {
disableElevation
size="large"
onClick={() => navigate('/create-mnemonic')}
data-testid="createAccount"
>
Create account
</Button>
+2 -3
View File
@@ -14,9 +14,8 @@ export const BalanceCard = () => {
return (
<NymCard
title="Balance"
data-testid="check-balance"
borderless
Action={<ClientAddress withCopy showEntireAddress />}
Action={<ClientAddress withCopy showEntireAddress/>}
>
<Grid container direction="column" spacing={2}>
<Grid item>
@@ -27,7 +26,7 @@ export const BalanceCard = () => {
)}
{!userBalance.error && (
<Typography
data-testid="refresh-success"
data-testid="nym-balance"
sx={{
color: 'text.primary',
textTransform: 'uppercase',
+1 -1
View File
@@ -30,7 +30,7 @@ export const Bond = () => {
<NymCard title="Bond" subheader="Bond a mixnode or gateway" noPadding>
{status === EnumRequestStatus.initial && (
<Box sx={{ px: 3, mb: 1 }}>
<Alert severity="warning">Always ensure you leave yourself enough funds to UNBOND</Alert>
<Alert severity="warning" data-testid="fundsAlert">Always ensure you leave yourself enough funds to UNBOND</Alert>
</Box>
)}
{ownership?.hasOwnership && (
+59
View File
@@ -0,0 +1,59 @@
import React, { useContext } from 'react';
import { Card, Divider, Grid, Typography } from '@mui/material';
import { useFormContext } from 'react-hook-form';
import { AppContext } from '../../context/main';
const SendReviewField = ({ title, subtitle, info }: { title: string; subtitle?: string; info?: boolean }) => (
<>
<Typography sx={{ color: info ? 'nym.fee' : '' }} data-testid={title}>
{title}
</Typography>
<Typography sx={{ color: info ? 'nym.fee' : '', wordBreak: 'break-all' }} data-testid={subtitle}>
{subtitle}
</Typography>
</>
);
export const SendReview = ({ transferFee }: { transferFee?: string }) => {
const { getValues } = useFormContext();
const { clientDetails } = useContext(AppContext);
const values = getValues();
return (
<Card
variant="outlined"
sx={{
width: '100%',
py: 3,
px: 2,
my: 3,
mx: 0,
}}
>
<Grid container spacing={2}>
<Grid item xs={12}>
<SendReviewField title="From" subtitle={clientDetails?.client_address} />
</Grid>
<Grid item xs={12}>
<Divider light />
</Grid>
<Grid item xs={12}>
<SendReviewField title="To" subtitle={values.to} />
</Grid>
<Grid item xs={12}>
<Divider light />
</Grid>
<Grid item xs={12}>
<SendReviewField title="Amount" subtitle={`${values.amount.amount} ${clientDetails?.denom}`} />
</Grid>
<Grid item xs={12}>
<Divider light />
</Grid>
<Grid item xs={12}>
<SendReviewField title="Transfer fee" subtitle={`${transferFee} ${clientDetails?.denom}`} info />
</Grid>
</Grid>
</Card>
);
};
+1 -1
View File
@@ -99,7 +99,7 @@ const TerminalInner: React.FC = () => {
<Box width="100%" display="flex" justifyContent="space-between">
<Box display="flex" alignItems="center">
<TerminalIcon sx={{ mr: 1 }} />
<Typography mr={4}>Terminal</Typography>
<Typography mr={4} data-testid='terminal-header'>Terminal</Typography>
{!isBusy && <RefreshIcon onClick={refresh} cursor="pointer" />}
</Box>
<CloseIcon onClick={handleShowTerminal} cursor="pointer" />
+1 -1
View File
@@ -19,5 +19,5 @@
"@assets/*": ["../assets/*"]
}
},
"exclude": ["node_modules", "dist", "jest.config.js", "webpack.config.js", "webpack.prod.js", "webpack.common.js", "target"]
"exclude": ["node_modules", "dist", "jest.config.js", "webpack.config.js", "webpack.prod.js", "webpack.common.js", "target", "wallet-ui-tests"]
}
@@ -0,0 +1,86 @@
import Balance from '../tests/pageobjects/balanceScreen'
import Auth from '../tests/pageobjects/authScreens'
const userData = require("../common/user-data.json");
const deleteScript = require("../scripts/deletesavedwallet")
const savedWalletScript = require("../scripts/savedwalletexists")
class Helpers {
// clear wallet data, login, and navigate to QA network
freshMnemonicLoginQaNetwork = async () => {
await deleteScript
await savedWalletScript
await Auth.loginWithMnemonic(userData.mnemonic)
await Balance.selectQa()
}
loginMnemonic = async () => {
await Auth.loginWithMnemonic(userData.mnemonic)
}
//helper to decode mnemonic so plain 24 character passphrase isn't in sight albeit it is presented when ruunning the scripts
// TO-DO figure out what's going on with the decoding bit
decodeBase = async (input) => {
var m = Buffer.from(input, "base64").toString();
return m;
}
navigateAndClick = async (element) => {
await element.waitForClickable({ timeout: 6000 })
await element.click();
}
elementVisible = async (element) => {
await element.waitForDisplayed({ timeout: 6000 })
}
elementClickable = async (element) => {
await element.toBeClickable({ timeout: 8000 })
}
addValueToTextField = async (element, value) => {
await element.addValue(value)
}
verifyStrictText = async (element, expectedText) => {
let error = await element.getText()
expect(error).toStrictEqual(expectedText)
}
verifyPartialText = async (element, expectedText) => {
let error = await element.getText()
expect(error).toContain(expectedText)
}
currentBalance = async (value) => {
return parseFloat(value.split(/\s+/)[0].toString()).toFixed(5)
}
calculateFees = async (beforeBalance, transactionFee, amount, isSend) => {
let fee
if (isSend) {
//send transaction
fee = transactionFee.split(/\s+/)[0]
} else {
//delegate transaction
fee = transactionFee.split(/\s+/)[3]
}
const currentBalance = beforeBalance.split(/\s+/)[0]
console.log("currenttttt 2 ............. = " + currentBalance)
const castCurrentBalance = parseFloat(currentBalance).toFixed(5)
console.log("castttt ............. " + castCurrentBalance)
const transCost = +parseFloat(amount) + +parseFloat(fee).toFixed(5)
console.log("trans ............." + transCost)
let sum = +castCurrentBalance - transCost
return sum.toFixed(5)
}
}
module.exports = new Helpers();
@@ -0,0 +1,42 @@
module.exports = {
//welcome, sign in, create account
homePageErrorMnemonic: "Error parsing bip39 mnemonic",
signInWithoutMnemonic: "A mnemonic must be provided",
signInRandomString: "mnemonic has a word count that is not a multiple of 6:",
signInIncorrectMnemonic: "mnemonic contains an unknown word",
incorrectMnemonicPasswordCreation: "The mnemonic provided is not valid. Please check the mnemonic",
invalidPasswordOnSignIn: "failed to decrypt the given data with the provided password",
signInWithoutPassword: "A password must be provided",
failedToFindWalletFile: "The wallet file is not found",
//headers
mnemonicSignIn: "Enter a mnemonic to sign in",
passwordSignIn: "Enter a password to sign in",
//homePage
qaNetwork: "QA",
sandboxNetwork: "Testnet Sandbox",
mainnetNetwork: "Nym Mainnet",
noNym: "0 NYM",
//send
invalidRecipientAddress: "123",
recipientAddress: "n17tj0a0w6v7r2dc54rnkzfza6s8hxs87rj273a5",
amountToSend: "1",
negativeAmount: "-1",
inferiorAmount: "0.0000001",
confirmedAmount: "1 NYM",
sendDetails: "Send details",
// bond
host: "1.1.1.1",
version: "1.2.1",
// user incorrect data
incorrectMnemonic: "giraffe note order sun cradle bottom crime humble able antique rural donkey guess parent potato tongue truly way disagree exile zebra someone else heat",
randomString:"thisrandomstring",
password:"iAmThePassword1!",
incorrectPassword:"123notvalid",
};
@@ -0,0 +1,9 @@
{
"mnemonic": "giraffe note order sun cradle bottom crime humble able antique rural donkey guess parent potato tongue truly way disagree exile zebra someone else typical",
"qa_address": "n1qqct7gs79yrjncpkumljxeqjsnwvn42j2g3fw4",
"receiver_address": "n167rupnmpput2alw62sz43eelks03zek4fwvjk0",
"amount_to_send": "1",
"identity_key_to_delegate_mix_node": "HqW2HStFHtAZ3PxRaiSCh7xJK6B7swoR1gSmJzH2iV9g",
"identity_key_to_delegate_gateway": "",
"delegate_amount": "10"
}
+31
View File
@@ -0,0 +1,31 @@
{
"name": "wallet-ui-tests",
"version": "1.0.0",
"description": "ui tests for the nym wallet",
"scripts": {
"test": "wdio run wdio.conf.ts",
"test:signup": "wdio run wdio.conf.ts --suite signup",
"test:login": "wdio run wdio.conf.ts --suite login",
"test:balance": "wdio run wdio.conf.ts --suite balance",
"test:nav": "wdio run wdio.conf.ts --suite nav",
"test:send": "wdio run wdio.conf.ts --suite send",
"test:delegation": "wdio run wdio.conf.ts --suite delegation"
},
"author": "",
"license": "MIT",
"dependencies": {
"-": "^0.0.1",
"@wdio/cli": "^7.16.16",
"save-dev": "^0.0.1-security",
"@zxing/browser": "^0.0.9",
"ts-node": "^10.6.0"
},
"devDependencies": {
"@wdio/local-runner": "^7.16.16",
"@wdio/mocha-framework": "^7.16.15",
"@wdio/spec-reporter": "^7.16.14",
"prettier": "2.5.1",
"typescript": "^4.6.2"
}
}
@@ -0,0 +1,9 @@
const { exec } = require("child_process")
const deleteSavedFile = exec("rm '/home/benedetta/.local/share/nym-wallet/saved-wallet.json'", (err, stdout, stderr) => {
if (err) {
console.error(`${err.message}`)
return
} else
console.log("File deleted")
})
@@ -0,0 +1,14 @@
const { exec } = require("child_process")
// const doesFileExist = exec("test -f /home/benedetta/.local/share/nym-wallet/saved-wallet.json" && "echo '$FILE exists.'" || "echo 'file doesn't exist'")
// scriptExist ? expect(getErrorWarning).toStrictEqual(textConstants.invalidPasswordOnSignIn) : expect(getErrorWarning).toStrictEqual(textConstants.failedToFindWalletFile)
const doesFileExist = exec("test -f /home/benedetta/.local/share/nym-wallet/saved-wallet.json", (err, stdout, stderr) => {
if (err) {
console.error(`${err.message}`)
return
} else
console.log("File: " + stdout)
})
@@ -0,0 +1,21 @@
class Nav {
get lightMode() { return $("[data-testid='LightModeOutlinedIcon']") }
get darkMode() { return $("[data-testid='ModeNightOutlinedIcon']") }
get terminalTitle() { return $("[data-testid='terminal-header']") }
get terminalIcon() { return $("[data-testid='TerminalIcon']") }
get balance() { return $("[data-testid='Balance']") }
get send() { return $("[data-testid='Send']") }
get receive() { return $("[data-testid='Receive']") }
get bond() { return $("[data-testid='Bond']") }
get unbond() { return $("[data-testid='Unbond']") }
get delegation() { return $("[data-testid='Delegation']") }
get closeIcon() { return $("[data-testid='CloseIcon']") }
}
export default new Nav()
@@ -0,0 +1,74 @@
import Balance from '../pageobjects/balanceScreen'
class Auth {
//Welcome landing page
get signInButton() { return $("[data-testid='signIn']") }
get createAccount() { return $("[data-testid='createAccount']") }
// Existing account sign in option page
get signInMnemonic() { return $("[data-testid='signInWithMnemonic']") }
get signInPassword() { return $("[data-testid='signInWithPassword']") }
get backToWelcomePage() { return $("[data-testid='backToWelcomePage']") }
get forgotPassword() { return $("[data-testid='forgotPassword']") }
// Sign in with mnemonic page
get mnemonicLoginScreenHeader() { return $("[data-testid='Enter a mnemonic to sign in']") }
get mnemonicInput() { return $("[data-testid='mnemonicInput']") }
get signIn() { return $("[data-testid='signInSubmitButton']") }
get backToSignInOptions() { return $("[data-testid='backToSignInOptions']") }
get createPassword() { return $("[data-testid='goToCreatePassword']") }
// Create password step 1/2
get backToMnemonicSignIn() { return $("[data-testid='backToMnemonicSignIn']") }
get nextToPasswordCreation() { return $("[data-testid='nextToPasswordCreation']") }
// Create password step 2/2
get password() { return $("[data-testid='Password']") }
get confirmPassword() { return $("[data-testid='Confirm password']") }
get createPasswordButton() { return $("[data-testid='createPasswordButton']") }
get backToStep1PasswordCreation() { return $("[data-testid='backToStep1PasswordCreation']") }
// Create account step 1/3
get copyMnemonic() { return $("[data-testid='copyMnemonic']") }
get iSavedMnemonic() { return $("[data-testid='iSavedMnemonic']") }
get mnemonicPhrase() { return $("[data-testid='mnemonicPhrase']") }
get backToWelcomePageFromCreate() { return $("[data-testid='backToWelcome']") }
// Create account step 2/3
get wordIndex() { return $$("[data-testid='wordIndex']") }
get mnemonicWordTile() { return $$("[data-testid='mnemonicWordTile']") }
get nextToStep3() { return $("[data-testid='nextToStep3']") }
get backToStep1() { return $("[data-testid='backToStep1']") }
// Create account step 3/3
get nextStorePassword() { return $("[data-testid='nextStorePassword']") }
get skipPasswordAndSignInWithMnemonic() { return $("[data-testid='skipPasswordAndSignInWithMnemonic']") }
// Enter password to sign in
get passwordLoginScreenHeader() { return $("[data-testid='Enter a password to sign in']") }
get enterPassword() { return $("[data-testid='Enter password']") }
get signInPasswordButton() { return $("[data-testid='signInPasswordButton']") }
get backToSignInOptionsFromPassword() { return $("[data-testid='backToSignInOptionsFromPassword']") }
get forgotPasswordButton() { return $("[data-testid='forgotPasswordButton']") }
// Errors
get error() { return $("[data-testid='error']") }
//TO-DO get this bit below working
getErrorMessage = async () => {
await (await this.error).waitForDisplayed({ timeout: 1500 })
await this.error.getText()
}
//login to the application
loginWithMnemonic = async (mnemonic) => {
await this.signInButton.click()
await this.signInMnemonic.click()
await this.mnemonicInput.waitForDisplayed()
await this.mnemonicInput.addValue(mnemonic);
await this.signIn.click();
await Balance.nymBalance.isExisting({ timeout: 4000 });
};
}
export default new Auth()
@@ -0,0 +1,23 @@
class Balance {
get balance() { return $("[data-testid='Balance']") }
get checkBalance() { return $("[data-testid='check-balance']") }
get nymBalance() { return $("[data-testid='nym-balance']") }
get copyAccountId() { return $("[data-testid='copyIcon']") }
get accountNumber() { return $("[data-testid='accountNumber']") }
get networkDropdown() { return $("[data-testid='ArrowDropDownIcon']") }
get networkEnv() { return $("[data-testid='networkEnv']") }
get networkSelectQa() { return $("[data-testid='QA']") }
selectQa = async () => {
await this.networkDropdown.waitForDisplayed({ timeout: 4000 })
await this.networkDropdown.click()
await this.networkSelectQa.waitForClickable({ timeout: 4000 })
await this.networkSelectQa.click()
await this.networkEnv.waitForClickable({ timeout: 2000 })
}
}
export default new Balance()
@@ -0,0 +1,10 @@
class Bond {
get bondTitle() { return $("[data-testid='Bond']") }
get mixnodeRadio() { return $("[data-testid='mix-node']") }
get gatewayRadio() { return $("[data-testid='gate-way']") }
get fundsAlert() { return $("[data-testid='fundsAlert']") }
}
export default new Bond()
@@ -0,0 +1,9 @@
class Delegation {
get delegationTitle() { return $("[data-testid='Delegation']") }
get delegateStakeButton() { return $("[data-testid='Delegate stake']") }
get delegateModalHeader() { return $("[data-testid='Delegate']") }
}
export default new Delegation()
@@ -0,0 +1,7 @@
class Receive {
get receiveNymTitle() { return $("[data-testid='Receive NYM']") }
}
export default new Receive()
@@ -0,0 +1,28 @@
class Send {
// send nym form
get sendHeader() { return $("[data-testid='Send']") }
get recipientAddress() { return $("[data-testid='recipientAddress']") }
// get sendAmount() { return $("[data-testid='Amount']") }
get sendAmount() { return $("#mui-5") } // TO-DO fix this selector, using #mui-5 isn't a good solution
get next() { return $("[data-testid='Next']") }
// confirm transaction modal
get sendDetailsHeader() { return $("[data-testid='Send details']") }
get from() { return $("/html/body/div[2]/div[3]/div[2]/div[1]/div[1]") }
get to() { return $("/html/body/div[2]/div[3]/div[2]/div[2]") }
get amount() { return $("/html/body/div[2]/div[3]/div[2]/div[3]") }
get fee() { return $("/html/body/div[2]/div[3]/div[2]/div[4]") }
get confirm() { return $("[data-testid='Confirm']") }
// transaction sent
get viewOnBlockchain() { return $("[data-testid='viewOnBlockchain']") }
get done() { return $("[data-testid='Done']") }
}
export default new Send()
@@ -0,0 +1,7 @@
class Unbond {
get unbondTitle() { return $("[data-testid='Unbond']") }
}
export default new Unbond()
@@ -0,0 +1,32 @@
import Balance from '../../pageobjects/balanceScreen'
import Auth from '../../pageobjects/authScreens'
const textConstants = require("../../../common/text-constants");
const userData = require("../../../common/user-data.json");
const Helper = require('../../../common/helper');
describe('Balance screen displays correctly', () => {
it('selecting qa network', async () => {
//log in
await Helper.loginMnemonic()
// select QA network
await Helper.navigateAndClick(Balance.networkDropdown)
await Helper.navigateAndClick(Balance.networkSelectQa)
// verifty QA network has been selected properly
await Helper.verifyStrictText(Balance.networkEnv, textConstants.qaNetwork)
})
it('copy the account id', async () => {
// ensure the account number contains *something*
await Helper.elementVisible(Balance.accountNumber)
await Helper.verifyPartialText(Balance.accountNumber[1],'1')
await Helper.navigateAndClick(Balance.copyAccountId)
// TO-DO is there a way to verify that the copy worked, aka pasting it somewhere maybe?
})
})
@@ -0,0 +1,21 @@
import Balance from '../../pageobjects/balanceScreen'
import Auth from '../../pageobjects/authScreens'
import Nav from '../../pageobjects/appNavConstants'
import Delegation from '../../pageobjects/delegationScreen'
import Send from '../../pageobjects/sendScreen'
const Helper = require('../../../common/helper');
const textConstants = require("../../../common/text-constants");
const userData = require("../../../common/user-data.json");
describe('Delegate to a mixnode', () => {
it('entering an invalid node identity key', async () => {
//login and navigate to the screen
await Helper.freshMnemonicLoginQaNetwork()
await Helper.navigateAndClick(Nav.delegation)
await Helper.elementVisible(Delegation.delegationTitle)
// TO-DO enter an invalid node
})
})
@@ -0,0 +1,99 @@
import Auth from '../../pageobjects/authScreens'
import Balance from '../../pageobjects/balanceScreen'
import ValidatorClient from '@nymproject/nym-validator-client';
const deleteScript = require("../../../scripts/deletesavedwallet")
const textConstants = require("../../../common/text-constants");
const userData = require("../../../common/user-data.json");
const Helper = require('../../../common/helper');
describe('Create password for existing account and use it to sign in', () => {
it('enter incorrect mnemonic', async () => {
//click through sign in
await Helper.navigateAndClick(Auth.signInButton)
await Helper.navigateAndClick(Auth.signInMnemonic)
//instead of entering mnemonic, click on create a password
await Helper.navigateAndClick(Auth.createPassword)
//enter incorrect mnemonic
await Helper.addValueToTextField(Auth.mnemonicInput, textConstants.incorrectMnemonic)
await Helper.navigateAndClick(Auth.nextToPasswordCreation)
// assert error message is correct
await Helper.verifyStrictText(Auth.error, textConstants.incorrectMnemonicPasswordCreation)
})
it('enter random string', async () => {
// enter random string as mnemonic
await Helper.addValueToTextField(Auth.mnemonicInput, textConstants.randomString)
await Helper.navigateAndClick(Auth.nextToPasswordCreation)
// assert error is correct
await Helper.verifyStrictText(Auth.error, textConstants.incorrectMnemonicPasswordCreation)
})
it('enter correct mnemonic', async () => {
// generate random mnemonic in the backend
const randomMnemonic = ValidatorClient.randomMnemonic();
deleteScript
// use it to continue with password creation flow
await Helper.navigateAndClick(Auth.backToMnemonicSignIn)
await Helper.navigateAndClick(Auth.createPassword)
await Helper.addValueToTextField(Auth.mnemonicInput, randomMnemonic)
await Helper.navigateAndClick(Auth.nextToPasswordCreation)
await Helper.elementVisible(Auth.password)
})
it('create an invalid password', async () => {
// type an invalid password in both fields
await Helper.addValueToTextField(Auth.password, textConstants.incorrectPassword)
await Helper.navigateAndClick(Auth.confirmPassword)
await Helper.addValueToTextField(Auth.confirmPassword, textConstants.incorrectPassword)
// ensure the button to proceed is still disabled
const nextButton = await Auth.createPasswordButton
const isNextDisabled = await nextButton.getAttribute('disabled')
expect(isNextDisabled).toBe("true")
})
it('create a valid password', async () => {
// type a valid password in both fields
await Helper.navigateAndClick(Auth.password)
await Helper.addValueToTextField(Auth.password, textConstants.password)
await Helper.navigateAndClick(Auth.confirmPassword)
await Helper.addValueToTextField(Auth.confirmPassword, textConstants.password)
// verify the password is created and the next screen is visible
await Helper.navigateAndClick(Auth.createPasswordButton)
await Helper.verifyStrictText(Auth.passwordLoginScreenHeader, textConstants.passwordSignIn)
})
it('sign in with no password throws error', async () => {
//click sign without entering a password
await Helper.navigateAndClick(Auth.signInPasswordButton)
// wait for error
await Helper.elementVisible(Auth.error)
// verify error has the correct message
await Helper.verifyStrictText(Auth.error, textConstants.signInWithoutPassword)
})
it('sign in with invalid password throws error', async () => {
// enter invalid password
await Helper.addValueToTextField(Auth.enterPassword, textConstants.incorrectPassword)
await Helper.navigateAndClick(Auth.signInPasswordButton)
// wait for error
await Helper.elementVisible(Auth.error)
await Helper.verifyStrictText(Auth.error, textConstants.invalidPasswordOnSignIn)
})
})
@@ -0,0 +1,67 @@
import Auth from '../../pageobjects/authScreens'
import Balance from '../../pageobjects/balanceScreen'
import ValidatorClient from '@nymproject/nym-validator-client';
import { text } from 'stream/consumers';
const textConstants = require("../../../common/text-constants");
const userData = require("../../../common/user-data.json");
const Helper = require('../../../common/helper');
describe('Wallet sign in functionality with mnemonic', () => {
it('get to the sign in with mnemonic screen', async () => {
// click through to reach the mnemonic sign in
await Helper.navigateAndClick(Auth.signInButton)
await Helper.navigateAndClick(Auth.signInMnemonic)
// verify you are on the right screen by confirming the header
await Helper.verifyStrictText(Auth.mnemonicLoginScreenHeader, textConstants.mnemonicSignIn)
})
it('sign in with no mnemonic throws error', async () => {
await Helper.navigateAndClick(Auth.signIn)
// wait for error
await Helper.elementVisible(Auth.error)
// verify error has the correct message
await Helper.verifyStrictText(Auth.error, textConstants.signInWithoutMnemonic)
})
it('sign in with incorrect mnemonic throws error', async () => {
// enter an incorrect mnemonic string
await Helper.addValueToTextField(Auth.mnemonicInput, textConstants.incorrectMnemonic)
await Helper.navigateAndClick(Auth.signIn)
// verifty error message is correct
await Helper.verifyPartialText(Auth.error, textConstants.signInIncorrectMnemonic)
})
it('sign in with random string throws error', async () => {
// enter a random string not in mnemonic "format"
await Helper.addValueToTextField(Auth.mnemonicInput, textConstants.randomString)
await Helper.navigateAndClick(Auth.signIn)
// verifty error message is correct
await Helper.verifyPartialText(Auth.error, textConstants.signInRandomString)
})
it('should sign in with valid credentials', async () => {
// create new mnemonic
const randomMnemonic = ValidatorClient.randomMnemonic();
// enter mnemonic
await Helper.addValueToTextField(Auth.mnemonicInput, randomMnemonic)
await Helper.navigateAndClick(Auth.signIn)
// verify successful login, balance is visible
await Helper.elementVisible(Balance.balance)
//new accounts will always default to mainnet, so 0 balance
// TO-DO this value sometimes returns " " instead of "0"
await Helper.verifyStrictText(Balance.nymBalance, textConstants.noNym)
})
})
@@ -0,0 +1,29 @@
import Auth from '../../pageobjects/authScreens'
import Balance from '../../pageobjects/balanceScreen'
const textConstants = require("../../../common/text-constants");
const userData = require("../../../common/user-data.json");
const deleteWallet = require("../../../scripts/deletesavedwallet");
const walletExists = require("../../../scripts/savedwalletexists")
const Helper = require('../../../common/helper');
describe('Wallet sign in functionality without creating password', () => {
it('sign in with invalid password and no saved wallet.json file throws error', async () => {
// delete existing saved wallet file
deleteWallet
//click through sign without entering a password
await Helper.navigateAndClick(Auth.signInButton)
await Helper.navigateAndClick(Auth.signInPassword)
// enter invalid password
await Helper.addValueToTextField(Auth.enterPassword,textConstants.incorrectPassword)
await Helper.navigateAndClick(Auth.signInPasswordButton)
// wait for error
await Helper.elementVisible(Auth.error)
// verify error has the correct message
await Helper.verifyStrictText(Auth.error, textConstants.failedToFindWalletFile)
})
})
@@ -0,0 +1,73 @@
import Auth from '../../pageobjects/authScreens'
import Nav from '../../pageobjects/appNavConstants'
import Balance from '../../pageobjects/balanceScreen'
import Send from '../../pageobjects/sendScreen'
import Receive from '../../pageobjects/receiveScreen'
import Bond from '../../pageobjects/bondScreen'
import Unbond from '../../pageobjects/unbondScreen'
import Delegation from '../../pageobjects/delegationScreen'
const userData = require("../../../common/user-data.json");
const Helper = require('../../../common/helper');
describe('Nav Items behave correctly', () => {
it('switch from light to dark mode and back', async () => {
//log in
await Helper.freshMnemonicLoginQaNetwork()
// click on different modes
await Helper.navigateAndClick(Nav.lightMode)
await Helper.navigateAndClick(Nav.darkMode)
await Helper.elementVisible(Nav.lightMode)
})
it('clicking terminal opens the modal', async () => {
// ensure the terminal button opens the terminal
await Helper.elementVisible(Nav.terminalIcon)
await Helper.navigateAndClick(Nav.terminalIcon)
await Helper.elementVisible(Nav.terminalTitle)
await Helper.verifyPartialText(Nav.terminalTitle, 'Terminal')
})
})
describe('Menu items lead to correct screen', () => {
//TO-DO none of this works
//check each menu item opens the right screen/modal
it('check Balance link works', async () => {
await Helper.navigateAndClick(Nav.balance)
await Helper.verifyPartialText(Balance.balance, 'Balance')
})
it('check Send link works', async () => {
await Helper.navigateAndClick(Nav.send)
await Helper.verifyPartialText(Send.sendHeader, 'Send')
await Helper.navigateAndClick(Nav.closeIcon)
})
it('check Receive link works', async () => {
await Helper.navigateAndClick(Nav.receive)
await Helper.verifyPartialText(Receive.receiveNymTitle, 'Receive NYM')
})
it('check Bond link works', async () => {
await Helper.navigateAndClick(Nav.bond)
await Helper.verifyPartialText(Bond.bondTitle, 'Bond')
})
it('check Unbond link works', async () => {
await Helper.navigateAndClick(Nav.unbond)
await Helper.verifyPartialText(Unbond.unbondTitle, 'Unbond')
})
it('check Delegation link works', async () => {
await Helper.navigateAndClick(Nav.delegation)
await Helper.verifyPartialText(Delegation.delegationTitle, 'Delegation')
})
})
@@ -0,0 +1,101 @@
import Auth from '../../pageobjects/authScreens'
import Balance from '../../pageobjects/balanceScreen'
const textConstants = require("../../../common/text-constants");
const userData = require("../../../common/user-data.json");
const deleteScript = require("../../../scripts/deletesavedwallet")
const Helper = require('../../../common/helper');
describe('Create a new account and verify it exists', () => {
it('generate new mnemonic and verify mnemonic words', async () => {
// delete any existing saved-wallet.json
deleteScript
// click through create account flow
await Helper.navigateAndClick(Auth.createAccount)
await Helper.elementVisible(Auth.mnemonicPhrase)
// save mnemonic phrase
let mnemonic = await (await Auth.mnemonicPhrase).getText()
let arrayMnemonic = mnemonic.split(" ")
await Helper.navigateAndClick(Auth.copyMnemonic)
await Helper.navigateAndClick(Auth.iSavedMnemonic)
// verify the mnemonic words in the correct order
let mnemonicWordTiles = await (await Auth.mnemonicWordTile)
let wordTileIndex = await (await Auth.wordIndex)
const wordsArray: any[] = []
for (const word of mnemonicWordTiles) {
const wordText = await word.getText()
const index = arrayMnemonic.indexOf(wordText)
wordsArray.push({ word, index })
}
for (const index of wordTileIndex) {
const indexValue = await index.getText()
const match = wordsArray.find((word) => +word.index === +indexValue - 1)
if (match) {
await match.word.click()
}
}
// ensure that once the task above is complete, the 'next' button is enabled
const nextButton = await Auth.nextToStep3
const isNextDisabled = await nextButton.getAttribute('disabled')
expect(isNextDisabled).toBe(null)
})
it('click skip password', async () => {
// click on skip password creation
await Helper.navigateAndClick(Auth.nextToStep3)
await Helper.navigateAndClick(Auth.skipPasswordAndSignInWithMnemonic)
// can see mnemonic login page
await Helper.elementVisible(Auth.mnemonicInput)
await Helper.navigateAndClick(Auth.backToSignInOptions)
})
it('set up invalid password for new account', async () => {
// enter invalid password in both fields
await Helper.navigateAndClick(Auth.password)
await Helper.addValueToTextField(Auth.password, textConstants.incorrectPassword)
await Helper.navigateAndClick(Auth.confirmPassword)
await Helper.addValueToTextField(Auth.confirmPassword, textConstants.incorrectPassword)
// verify that the 'next' button is still disabled
const nextButton = await Auth.nextStorePassword
const isNextDisabled = await nextButton.getAttribute('disabled')
expect(isNextDisabled).toBe("true")
})
it('set up valid password for new account', async () => {
// enter a valid password in both fields
await Helper.navigateAndClick(Auth.password)
await Helper.addValueToTextField(Auth.password, textConstants.password)
await Helper.navigateAndClick(Auth.confirmPassword)
await Helper.addValueToTextField(Auth.confirmPassword, textConstants.password)
// verify that the 'next' button is clickable
const nextButton = await Auth.nextStorePassword
const isNextDisabled = await nextButton.getAttribute('disabled')
expect(isNextDisabled).toBe(null)
})
it('proceed to login with newly created password', async () => {
// login with a password
await Helper.navigateAndClick(Auth.nextStorePassword)
await Helper.navigateAndClick(Auth.enterPassword)
await Helper.addValueToTextField(Auth.enterPassword, textConstants.password)
await Helper.navigateAndClick(Auth.signInPasswordButton)
// TO-DO for some reason this is failing due to failed to decrypt the wallet etc error
await Helper.elementVisible(Balance.balance)
//new accounts will always default to mainnet, so 0 balance
await Helper.verifyStrictText(Balance.nymBalance, textConstants.noNym)
})
})
@@ -0,0 +1,62 @@
import Balance from '../../pageobjects/balanceScreen'
import Auth from '../../pageobjects/authScreens'
import Nav from '../../pageobjects/appNavConstants'
import Send from '../../pageobjects/sendScreen'
const Helper = require('../../../common/helper');
const textConstants = require("../../../common/text-constants");
const userData = require("../../../common/user-data.json");
describe.skip('Send modal functions correctly', () => {
it('entering an invalid recipient address shows error', async () => {
// sign in with mnemonic and select QA
await Helper.freshMnemonicLoginQaNetwork()
// click on send and check modal appears
await Helper.navigateAndClick(Nav.send)
await Helper.elementVisible(Send.sendHeader)
// add an invalid recipient address
await Helper.addValueToTextField(Send.recipientAddress, textConstants.invalidRecipientAddress)
// TO-DO -- question: should there not be an error message before clicking on Next to warn that the address is invalid?
})
it('entering an valid recipient address with negative amount value shows error', async () => {
await Helper.navigateAndClick(Send.recipientAddress)
// TO-DO figure out how to clear a text field before adding new value
await (Send.recipientAddress).clearValue()
await Helper.addValueToTextField(Send.recipientAddress, userData.receiver_address)
await Helper.navigateAndClick(Send.sendAmount)
await Helper.addValueToTextField(Send.sendAmount, textConstants.negativeAmount)
//next button is still disabled and error message appears
const nextButton = await Send.next
const isNextDisabled = await nextButton.getAttribute('disabled')
expect(isNextDisabled).toBe("true")
})
it('enter a valid recipient and value', async () => {
// enter valid data
await Helper.addValueToTextField(Send.recipientAddress, userData.receiver_address)
const getCurrentBalance = await (await Balance.nymBalance).getText()
await Helper.addValueToTextField(Send.sendAmount, textConstants.amountToSend)
// click on next and verify details
await Helper.navigateAndClick(Send.next)
const fee = await (await Send.fee).getText()
await Helper.verifyPartialText(Send.sendDetailsHeader, textConstants.sendDetails)
await Helper.verifyPartialText(Send.amount, textConstants.confirmedAmount)
await Helper.navigateAndClick(Send.confirm)
await Helper.elementVisible(Send.viewOnBlockchain)
await Helper.elementClickable(Send.done)
// calculate the transaction and verify it has been correctly executed
let sumCost = await Helper.calculateFees(getCurrentBalance, fee, textConstants.amountToSend, true)
const getNewBalance = await Balance.nymBalance.getText()
await Helper.navigateAndClick(Send.done)
// TO-DO the following fails with "TypeError: elem[prop] is not a function"
expect(getNewBalance).toEqual(sumCost)
})
})
@@ -0,0 +1,30 @@
import ValidatorClient from '@nymproject/nym-validator-client';
describe.skip('Creating valid account', () => {
it('create mnemonic', async () => {
const benny = "giraffe note order sun cradle bottom crime humble able antique rural donkey guess parent potato tongue truly way disagree exile zebra someone else typical";
const mnemonic = ValidatorClient.randomMnemonic();
console.log(ValidatorClient);
const newAccountClient = await ValidatorClient.connect(mnemonic,
'https://qa-validator.nymtech.net', 'https://qa-validator-api.nymtech.net/api', 'n', 'n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep', 'n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav', 'nym');
const address = newAccountClient.address;
console.log({ address, mnemonic });
const client = await ValidatorClient.connect(
benny, 'https://qa-validator.nymtech.net', 'https://qa-validator-api.nymtech.net/api', 'n', 'n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep', 'n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav', 'nym');
await client.send(address, [{ amount: '10000000', denom: 'unym' }]);
const balance = await client.getBalance(address);
console.log({ balance });
expect(Number.parseFloat(balance.amount)).toBe(10000000);
}).timeout(5000);
})
// the newly created address from the test above:
// address: 'n13l7rwrygs0m3kx3en2eh55dtmwlzm0vskw0hxq',
// mnemonic: 'tree upset require kitten inquiry truck emotion ladder reject elbow page ability spot win board frog child much credit pizza picture hover medal zoo'
// always make sure it's on QA, unless youre on debug branch (~look in nym_path wdio.config.ts to check)
// ENABLE_QA_MODE=true target/release/nym-wallet
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"types": [
"node",
"webdriverio/async",
"@wdio/mocha-framework",
"expect-webdriverio",
"./wdio.conf.ts"
],
"target": "ES5"
},
"include": [
"wallet-ui-tests/*"
]
}
+103
View File
@@ -0,0 +1,103 @@
const os = require('os')
const path = require('path')
const { spawn, spawnSync } = require('child_process')
//insert path to binary
const nym_path = '../target/debug/nym_wallet'
let tauriDriver: any
exports.config = {
autoCompileOpts: {
autoCompile: true,
tsNodeOpts: {
transpileOnly: true,
project: 'tsconfig.json',
},
},
specs: ['./tests/specs/**/*.ts'],
suites: {
signup: [
'./tests/specs/newaccount/*.ts',
],
login: [
'./tests/specs/existingaccount/*.ts',
],
balance: [
'./tests/specs/balance/*.ts',
],
nav: [
'./tests/specs/navbaritems/*.ts',
],
send: [
'./tests/specs/send/*.ts',
],
delegation: [
'./tests/specs/delegation/*.ts',
],
},
// Patterns to exclude.
exclude: [
// 'path/to/excluded/files'
],
maxInstances: 1,
capabilities: [
{
maxInstances: 1,
'tauri:options': {
application: nym_path,
},
},
],
//
// ===================
// Test Configurations
// ===================
// Define all options that are relevant for the WebdriverIO instance here
//
// Level of logging verbosity: trace | debug | info | warn | error | silent
logLevel: 'info',
bail: 0,
framework: 'mocha',
reporters: ['spec'],
mochaOpts: {
ui: 'bdd',
timeout: 60000,
},
// ===================
// Test Reporters
// ===================
// reporters: [
// [
// "allure",
// {
// outputDir: "allure-results",
// disableWebdriverStepsReporting: true,
// disableWebdriverScreenshotsReporting: true,
// },
// ],
// ],
// this is documentented in the readme - you will need to build the project first
// ensure the rust project is built since we expect this binary to exist for the webdriver sessions
//onPrepare: () => spawnSync("cargo", ["build", "--release"]),
// ensure we are running `tauri-driver` before the session starts so that we can proxy the webdriver requests
beforeSession: () =>
(tauriDriver = spawn(path.resolve(os.homedir(), '.cargo', 'bin', 'tauri-driver'), [], {
stdio: [null, process.stdout, process.stderr],
})),
// afterTest: function (
// test,
// context,
// { error, result, duration, passed, retries }
// ) {
// if (error) {
// browser.takeScreenshot();
// }
// },
// clean up the `tauri-driver` process we spawned at the start of the session
afterSession: () => tauriDriver.kill(),
}
-5
View File
@@ -1,5 +0,0 @@
reports
allure-results
node_modules
.vscode
.idea
-86
View File
@@ -1,86 +0,0 @@
<!--
Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
SPDX-License-Identifier: Apache-2.0
-->
# Nym Wallet Webdriverio testsuite
A webdriverio test suite implementation using tauri driver
with a page object model design. This is to provide quick iterative feedback
on the UI of the nym wallet. Currently, tauri-driver is available to run on Windows and Linux machines.
## Installation prerequisites
- `Yarn`
- `NodeJS >= v16.8.0`
- `Rust & cargo >= v1.56.1`
- `tauri-driver`
- `That you have an existing mnemonic and you can login to the app`
- `Have the details listed below to provide the user-data.json file`
## Key Information
- Please read the instructions on the `nym/tauri-wallet/README.md` in the root of the project on how to build the application
- Please ensure you have the relevant Webdriver kits installed on your machine -
```
linux:
sudo apt-get install -y webkit2gtk-driver
```
```
windows:
download msedgedriver.exe from https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
```
please visit [Tauri Studio](https://tauri.studio/en/docs/usage/guides/webdriver/introduction), this will specify the additional drivers you need
- The path to run the application is set in the `wdio.conf.js` which lives in the root directory
- Before running the suite you need to build the application and check that the application has
built successfully, if so, you will have an executable sitting in the target directory in `tauri-wallet/target/*/nym_wallet` (refer to point 1)
- The suite will not be able to detect elements on screen if you select a release build, however you can run tests against a release target
## Installation & usage
- `test excution happens inside /webdriver directory`
- `test data needs to be provided inside the user-data.json`
- `check the wdio.conf.cjs to see the test execution along with the path location of the binary`
```
example:
//mnemonic is a base64 enconded value, which is your 24 character passphrase, these values are for illustration purposes
{
"mnemonic" : "dGhpcyBpcyBhIHBhc3NwaHJhc2UK",
"punk_address" : "punk1f3dzkhmunma5ze5q952daxca6371989189",
"receiver_address" : "punk1p0ce82jxxglpmutvhq4mdwgcwf4avm5n1821982",
"amount_to_send" : "1",
"identity_key_to_delegate_mix_node": "value",
"identity_key_to_delegate_gateway" : "value",
"delegate_amount" : "1"
}
```
- `yarn test:runall` - the first test run will take some time to spin up be patient
- You can run tests individually by passing through the script situated in the package.json for example `yarn test:newuser`
Tests are categorised and run by their pages, they follow a sequential flow, if one test case fails before the next execution it may derail the next test.
//todo improve in near future
## Test reporting
Currently the tests use allure reporting, the configuration can be altered in the `wdio.conf.cjs`. At present it takes snapshots of any failing tests, the test output run can be seen in the allure-results directory
Tests ouput:
- <guid-testuite.xml>
- <guid-attachment.png>
If any tests fail in their test run it will produce the stack trace error along with the test in question
## TODO
_Disclaimer_: Still WIP
Implement error handling/ beforeTest() - validating json file exists with data for test execution
Currently this is dev'd against a Linux based OS, not tested against windows yet.
-12
View File
@@ -1,12 +0,0 @@
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "14",
},
},
],
],
};
@@ -1,30 +0,0 @@
module.exports = {
//receivePage
recievePageInformation:
"You can receive tokens by providing this address to the sender",
receivePageHeaderText: "Receive Nym",
//sendPage
sendPunk: "Send punk",
//homePage
homePageErrorMnemonic: "Error parsing bip39 mnemonic",
homePageSignIn: "Sign in",
createOne: "Create one",
walletSuccess:
"Please store your mnemonic in a safe place. You'll need it to access your wallet",
//bondPage // unbondPage
bondAlreadyNoded: "Looks like you already have a mixnode bonded.",
bondNodeHeaderText: "Bond a node or gateway",
unbondNodeHeaderText: "Unbond a mixnode or gateway",
unbondMixNodeText: "Looks like you already have a mixnode bonded.",
unbondMixNode: "UNBOND",
//delegatePage // undelegatePage
delegateHeaderText: "Delegate\nDelegate to mixnode",
nodeIdentityValidationText: "identity is a required field",
amountValidationText: "amount is a required field",
undelegateHeaderText: "Undelegate from a mixnode or gateway",
delegationComplete: "Delegation complete",
};
@@ -1,9 +0,0 @@
{
"mnemonic": "value",
"punk_address": "",
"receiver_address": "",
"amount_to_send": "",
"identity_key_to_delegate_mix_node": "",
"identity_key_to_delegate_gateway": "",
"delegate_amount": ""
}
@@ -1,43 +0,0 @@
class Helpers {
//helper to decode mnemonic so plain 24 character passphrase isn't in sight albeit it is presented when ruunning the scripts
//maybe a show passphrase toggle button?
decodeBase = async (input) => {
var m = Buffer.from(input, "base64").toString();
return m;
};
navigateAndClick = async (element) => {
await element.click();
};
scrollIntoView = async (element) => {
await element.scrollIntoView();
};
currentBalance = async (value) => {
return parseFloat(value.split(/\s+/)[0].toString()).toFixed(5);
};
//todo need to improve calculation - WIP
calculateFees = async (beforeBalance, transactionFee, amount, isSend) => {
let fee;
if (isSend) {
//send transaction
fee = transactionFee.split(/\s+/)[0];
} else {
//delegate transaction
fee = transactionFee.split(/\s+/)[3];
}
const currentBalance = beforeBalance.split(/\s+/)[0];
const castCurrentBalance = parseFloat(currentBalance).toFixed(5);
const transCost = +parseFloat(amount) + +parseFloat(fee).toFixed(5);
let sum = parseFloat(castCurrentBalance) - parseFloat(transCost);
return sum.toFixed(5);
};
}
module.exports = new Helpers();
-27
View File
@@ -1,27 +0,0 @@
{
"name": "tauri_nym_wallet",
"version": "1.0.0",
"private": false,
"license": "MIT",
"scripts": {
"test:runall": "wdio run wdio.conf.cjs",
"test:sendreceive": "wdio run wdio.conf.cjs --suite sendreceive",
"test:home": "wdio run wdio.conf.cjs --suite home",
"test:bond": "wdio run wdio.conf.cjs --suite bond",
"test:delegate": "wdio run wdio.conf.cjs --suite delegate",
"test:newuser": "wdio run wdio.conf.cjs --suite newuser",
"run:prettier": "prettier --write ."
},
"dependencies": {
"@types/node": "^16.11.0",
"@wdio/allure-reporter": "^7.16.1",
"@wdio/cli": "^7.9.1",
"@zxing/browser": "^0.0.9"
},
"devDependencies": {
"@wdio/local-runner": "^7.14.1",
"@wdio/mocha-framework": "^7.14.1",
"@wdio/spec-reporter": "^7.14.1",
"prettier": "2.4.1"
}
}
@@ -1,48 +0,0 @@
class WalletBond {
get header() {
return $(
"#root > div > div:nth-child(2) > div:nth-child(2) > div > div > div > div.MuiCardHeader-root > div > span.MuiTypography-root.MuiCardHeader-subheader.MuiTypography-subtitle1.MuiTypography-colorTextSecondary.MuiTypography-displayBlock"
);
}
get identityKey() {
return $("#identityKey");
}
get sphinxKey() {
return $("#sphinxKey");
}
get amountToBond() {
return $("#amount");
}
get hostInput() {
return $("#host");
}
get versionInput() {
return $("version");
}
get selectAdvancedOptions() {
return $("[type='checkbox']");
}
get mixPort() {
return $("#mixPort");
}
get verlocPort() {
return $("#verlocPort");
}
get httpApiPort() {
return $("#httpApiPort");
}
get bondButton() {
return $("[data-testid='bond-button']");
}
get unBondButton() {
return $("[data-testid='un-bond']");
}
get unBond() {
return $("[data-testid='bond-noded']");
}
get unBondWarning() {
return $("div.MuiAlert-message");
}
}
module.exports = new WalletBond();
@@ -1,24 +0,0 @@
class WalletCreate {
get createAccount() {
return $("[href='#']");
}
get create() {
return $("[data-testid='create-button']");
}
get accountCreatedSuccessfully() {
return $("[data-testid='mnemonic-warning']");
}
get walletMnemonicValue() {
return $("[data-testid='mnemonic-phrase']");
}
get punkAddress() {
return $("[data-testid='wallet-address']");
}
get backToSignIn() {
return $("[data-testid='sign-in-button']");
}
get signInButton() {
return $("[type='submit']");
}
}
module.exports = new WalletCreate();
@@ -1,60 +0,0 @@
class WalletDelegate {
get header() {
return $("[data-testid='Delegate']");
}
get nodeIdentity() {
return $("#identity");
}
get amountToDelegate() {
return $("#amount");
}
get identityValidation() {
return $("#identity-helper-text");
}
get amountToDelegateValidation() {
return $("#amount-helper-text");
}
get delegateStakeButton() {
return $("[data-testid='delegate-button']");
}
get mixNodeRadioButton() {
return $("[data-testid='mix-node']");
}
get gateWayRadioButton() {
return $("[data-testid='gate-way']");
}
get successfullyDelegate() {
return $("[data-testid='delegate-success']");
}
get finishButton() {
return $("[data-testid='finish-button']");
}
get transactionFeeAmount() {
return $("[data-testid='fee-amount']");
}
get accountBalance() {
return $("[data-testid='account-balance']");
}
//Undelegate
get unDelegateHeader() {
return $("[data-testid='Undelegate']");
}
get unNodeIdentity() {
return $("[name='identity']");
}
get unDelegateFeeText() {
return $("[data-testid='fee-amount']");
}
get unDelegateGatewayRadioButton() {
return $("[data-testid='gate-way']");
}
get unMixNodeRadioButton() {
return $("[data-testid='mix-node']");
}
get unDelegateButton() {
return $("[data-testid='submit-button']");
}
}
module.exports = new WalletDelegate();
@@ -1,42 +0,0 @@
class WalletHome {
get balanceCheck() {
return $(
"#root > div > div:nth-child(2) > div:nth-child(2) > div > div > div > div.MuiCardHeader-root > div > span"
);
}
get punkBalance() {
return $("");
}
get punkAddress() {
return $("[data-testid='wallet-address']");
}
get accountBalance() {
return $("[data-testid='account-balance']");
}
get balanceButton() {
return $("[href='/balance']");
}
get sendButton() {
return $("[href='/send']");
}
get receiveButton() {
return $("[href='/receive']");
}
get bondButton() {
return $("[href='/bond']");
}
get unBondButton() {
return $("[href='/unbond']");
}
get delegateButton() {
return $("[href='/delegate']");
}
get unDelegateButton() {
return $("[href='/undelegate']");
}
get logOutButton() {
return $("[data-testid='log-out']");
}
}
module.exports = new WalletHome();
@@ -1,31 +0,0 @@
class WalletLogin {
get signInLabel() {
return $("[data-testid='sign-in']");
}
get mnemonic() {
return $("#mnemonic");
}
get signInButton() {
return $("[type='submit']");
}
get errorValidation() {
return $("[class='MuiAlert-message']");
}
get accountBalance() {
return $("[data-test-id='account-balance']");
}
get accountBalanceText() {
return $("[class='MuiAlert-message']");
}
get walletAddress() {
return $("[data-testid='wallet-address']");
}
//login to the application
enterMnemonic = async (mnemonic) => {
await this.mnemonic.addValue(mnemonic);
await this.signInButton.click();
await this.accountBalance.isExisting();
};
}
module.exports = new WalletLogin();
@@ -1,37 +0,0 @@
class WalletReceive {
get receiveNymHeader() {
return $(
"#root > div > div:nth-child(2) > div:nth-child(2) > div > div > div > div.MuiCardHeader-root > div > span"
);
}
get receiveNymText() {
return $("[data-testid='receive-nym']");
}
get walletAddress() {
return $("[data-testid='client-address']");
}
get copyButton() {
return $("[data-testid='copy-button']");
}
get qrCode() {
return $("[data-testid='qr-code']");
}
WaitForButtonChangeOnCopy = async () => {
await this.copyButton.click();
await this.copyButton.waitForDisplayed({ timeout: 1500 });
await this.copyButton.waitUntil(
async function () {
return (await this.getText()) === "COPIED";
},
{
timeout: 1500,
timeoutMsg: "expected text to be different after 1.5s",
}
);
};
}
module.exports = new WalletReceive();
@@ -1,52 +0,0 @@
class WalletSend {
get fromAddress() {
return $("#from");
}
get toAddress() {
return $("#to");
}
get amount() {
return $("#amount");
}
get nextButton() {
return $("[data-testid='button");
}
get sendHeader() {
return $("[data-testid='Send punk']");
}
get accountBalance() {
return $("[data-testid='account-balance']");
}
get amountReviewAndSend() {
return $("[data-testid='Amount']");
}
get toAddressReviewAndSend() {
return $("[data-testid='To']");
}
get fromAddressReviewAndSend() {
return $("[data-testid='From']");
}
get transferFeeAmount() {
return $("[data-testid='Transfer fee']");
}
get reviewAndSendBackButton() {
return $("[data-testid='back-button']");
}
get sendButton() {
return $("[data-testid='button']");
}
get transactionComplete() {
return $("[data-testid='transaction-complete']");
}
get transactionCompleteRecipient() {
return $("[data-testid='to-address']");
}
get transactionCompleteAmount() {
return $("[data-testid='send-amount']");
}
get finishButton() {
return $("[data-testid='button']");
}
}
module.exports = new WalletSend();
@@ -1,22 +0,0 @@
class WallentUndelegate {
get transactionFee() {
return $("[data-testid='fee-amount']");
}
get mixNodeRadioButton() {
return $("[value='mixnode']");
}
get gatewayRadionButton() {
return $("[value='gateway']");
}
get nodeIdentity() {
return $("#mui-55011");
}
get identityHelper() {
return $("#identity-helper-text");
}
get delegateButton() {
return $("[data-testid='submit-button']");
}
}
module.exports = new WallentUndelegate();
@@ -1,54 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const helper = require("../../../common/helpers/helper");
const walletLogin = require("../../pages/wallet.login");
const textConstants = require("../../../common/constants/text-constants");
const walletHomepage = require("../../pages/wallet.homepage");
const bondPage = require("../../pages/wallet.bond");
describe("bonding and unbonding nodes", () => {
it("should have a node already bonded and validate no input fields are enabled", async () => {
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await helper.navigateAndClick(walletHomepage.bondButton);
await helper.scrollIntoView(bondPage.selectAdvancedOptions);
await bondPage.selectAdvancedOptions.click();
//as bond node is mixed expect all the fields to be disabled
const getText = await bondPage.header.getText();
const getIdentity = await bondPage.identityKey.isEnabled();
const getSphinxKey = await bondPage.sphinxKey.isEnabled();
const amountToBond = await bondPage.amountToBond.isEnabled();
const hostInput = await bondPage.hostInput.isEnabled();
const verlocPort = await bondPage.verlocPort.isEnabled();
const httpApiPort = await bondPage.httpApiPort.isEnabled();
const mixPort = await bondPage.mixPort.isEnabled();
//assert all field are not functional
expect(getText).toEqual(textConstants.bondNodeHeaderText);
expect(getIdentity).toEqual(false);
expect(getSphinxKey).toEqual(false);
expect(amountToBond).toEqual(false);
expect(hostInput).toEqual(false);
expect(verlocPort).toEqual(false);
expect(httpApiPort).toEqual(false);
expect(mixPort).toEqual(false);
});
it("unbond mix monde screen should be present with the option to unbond", async () => {
//we do not want to unbond our node, check that elements are selectable
await helper.scrollIntoView(walletHomepage.unBondButton);
await helper.navigateAndClick(walletHomepage.unBondButton);
const getText = await bondPage.header.getText();
const unbondText = await bondPage.unBondWarning.getText();
await bondPage.unBondButton.isClickable();
//assert all field are not functional
expect(getText).toEqual(textConstants.unbondNodeHeaderText);
expect(unbondText).toEqual(textConstants.unbondMixNodeText);
});
});
@@ -1,108 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const helper = require("../../../common/helpers/helper");
const walletLogin = require("../../pages/wallet.login");
const textConstants = require("../../../common/constants/text-constants");
const walletHomepage = require("../../pages/wallet.homepage");
const delegatePage = require("../../pages/wallet.delegate");
describe("delegate to a mix node or gateway", () => {
it("ensure that fields are enabled for existing user", async () => {
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await helper.navigateAndClick(walletHomepage.delegateButton);
const getText = await delegatePage.header.getText();
expect(getText).toEqual(textConstants.delegateHeaderText);
});
it("submitting the form without input prompts validation errors", async () => {
await delegatePage.delegateStakeButton.click();
const getIdentityValidation =
await delegatePage.identityValidation.getText();
const getAmountValidation =
await delegatePage.amountToDelegateValidation.getText();
expect(getIdentityValidation).toEqual(
textConstants.nodeIdentityValidationText
);
expect(getAmountValidation).toEqual(textConstants.amountValidationText);
});
it("input delegate amount to a mix node then broadcast the transaction then check account balances", async () => {
const balanceText = await delegatePage.accountBalance.getText();
const getTransfeeAmount = await delegatePage.transactionFeeAmount.getText();
await delegatePage.nodeIdentity.setValue(
userData.identity_key_to_delegate_mix_node
);
await delegatePage.amountToDelegate.setValue(userData.delegate_amount);
//transfer fee + amount delegation
const sumCost = await helper.calculateFees(
balanceText,
getTransfeeAmount,
userData.delegate_amount,
false
);
await delegatePage.delegateStakeButton.click();
await delegatePage.successfullyDelegate.waitForClickable({
timeout: 10000,
});
const getConfirmationText =
await delegatePage.successfullyDelegate.getText();
expect(getConfirmationText).toContain(textConstants.delegationComplete);
const availablePunk = await delegatePage.accountBalance.getText();
//expect new account balance - the fee calculation above
await delegatePage.finishButton.click();
expect(await helper.currentBalance(availablePunk)).toEqual(sumCost);
});
it("input amount to stake to a gateway then broadcast the transaction then check account balances", async () => {
const balanceText = await delegatePage.accountBalance.getText();
const getTransfeeAmount = await delegatePage.transactionFeeAmount.getText();
await delegatePage.gateWayRadioButton.click();
await delegatePage.nodeIdentity.setValue(
userData.identity_key_to_delegate_gateway
);
await delegatePage.amountToDelegate.setValue(userData.delegate_amount);
//transfer fee + amount delegation
const sumCost = await helper.calculateFees(
balanceText,
getTransfeeAmount,
userData.delegate_amount,
false
);
await delegatePage.delegateStakeButton.click();
await delegatePage.successfullyDelegate.waitForClickable({
timeout: 10000,
});
const getConfirmationText =
await delegatePage.successfullyDelegate.getText();
expect(getConfirmationText).toContain(textConstants.delegationComplete);
const availablePunk = await delegatePage.accountBalance.getText();
//expect new account balance - the fee calculation above
expect(await helper.currentBalance(availablePunk)).toEqual(sumCost);
});
});
@@ -1,45 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const helper = require("../../../common/helpers/helper");
const walletLogin = require("../../pages/wallet.login");
const homepPage = require("../../pages/wallet.homepage");
const textConstants = require("../../../common/constants/text-constants");
describe("wallet splash screen", () => {
it("should have the sign in header present", async () => {
const signInText = await walletLogin.signInLabel.getText();
expect(signInText).toEqual(textConstants.homePageSignIn);
});
it("submitting the sign in button with no input throws a validation error", async () => {
await walletLogin.signInButton.click();
const errorResponseText = await walletLogin.errorValidation.getText();
expect(errorResponseText).toEqual(textConstants.homePageErrorMnemonic);
});
//currently the punk_address is not fully displayed on the wallet UI
//trim the punk address
it("successfully input mnemonic and log in", async () => {
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await walletLogin.walletAddress.waitForEnabled({ timeout: 5000 });
const getWalletAddress = await walletLogin.walletAddress.getText();
//currently 35 characters are displayed along with three ...
//current hack we can assume this is the correct wallet
const walletTruncated = userData.punk_address.substring(0, 35);
expect(walletTruncated + "...").toContain(getWalletAddress);
});
it("successfully log out the application", async () => {
await helper.scrollIntoView(homepPage.logOutButton);
await homepPage.logOutButton.click();
await walletLogin.signInLabel.waitForEnabled({ timeout: 1500 });
expect(await walletLogin.signInLabel.isDisplayed()).toEqual(true);
});
});
@@ -1,28 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const textConstants = require("../../../common/constants/text-constants");
const helper = require("../../../common/helpers/helper");
const walletLogin = require("../../pages/wallet.login");
const receive = require("../../pages/wallet.receive");
const walletHomepage = require("../../pages/wallet.homepage");
describe("provide the relevant information about a user nym wallet address", () => {
it("should have the receivers address and a qr code present", async () => {
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await helper.navigateAndClick(walletHomepage.receiveButton);
await receive.receiveNymHeader.waitForDisplayed({ timeout: 1500 });
await receive.WaitForButtonChangeOnCopy();
const textHeader = await receive.receiveNymHeader.getText();
const getInformationText = await receive.receiveNymText.getText();
const getPunkAddress = await receive.walletAddress.getText();
expect(getPunkAddress).toEqual(userData.punk_address);
expect(getInformationText).toEqual(textConstants.recievePageInformation);
expect(textConstants.receivePageHeaderText).toEqual(textHeader);
});
});
@@ -1,55 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const helper = require("../../../common/helpers/helper");
const textConstants = require("../../../common/constants/text-constants");
const walletLogin = require("../../pages/wallet.login");
const sendWallet = require("../../pages/wallet.send");
const walletHomepage = require("../../pages/wallet.homepage");
describe("send punk to another a wallet", () => {
it("expect send screen to display the data", async () => {
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await helper.navigateAndClick(walletHomepage.sendButton);
const textHeader = await sendWallet.sendHeader.getText();
expect(textHeader).toContain(textConstants.sendPunk);
});
it("send funds correctly to another punk address", async () => {
//already logged in due to the previous test
const getCurrentBalance = await walletHomepage.accountBalance.getText();
await sendWallet.toAddress.addValue(userData.receiver_address);
await sendWallet.amount.addValue(userData.amount_to_send);
await sendWallet.nextButton.waitForEnabled({ timeout: 3000 });
await sendWallet.nextButton.click();
const transFee = await sendWallet.transferFeeAmount.getText();
await sendWallet.sendButton.click();
await sendWallet.finishButton.waitForClickable({ timeout: 10000 });
let sumCost = await helper.calculateFees(
getCurrentBalance,
transFee,
userData.amount_to_send,
true
);
await walletHomepage.accountBalance.isDisplayed();
const availablePunk = await walletHomepage.accountBalance.getText();
await sendWallet.finishButton.click();
//expect new account balance - the fee calculation above
expect(await helper.currentBalance(availablePunk)).toEqual(sumCost);
});
});
@@ -1,32 +0,0 @@
const userData = require("../../../common/data/user-data.json");
const helper = require("../../../common/helpers/helper");
const walletLogin = require("../../pages/wallet.login");
const walletHomepage = require("../../pages/wallet.homepage");
const unDelegatePage = require("../../pages/wallet.delegate");
describe("un-delegate a mix node or gateway", () => {
it("ensure that fields are enabled for existing user", async () => {
//we are ensuring that the fields are selectable for undelegation
//not proceeding to undelegate a node or gateway
const mnemonic = await helper.decodeBase(userData.mnemonic);
await walletLogin.enterMnemonic(mnemonic);
await helper.scrollIntoView(walletHomepage.unDelegateButton);
await helper.navigateAndClick(walletHomepage.unDelegateButton);
await unDelegatePage.unDelegateButton.waitForClickable({ timeout: 1500 });
await unDelegatePage.unDelegateButton.isEnabled();
await unDelegatePage.unDelegateGatewayRadioButton.click();
await unDelegatePage.unDelegateGatewayRadioButton.isSelected();
const mixNodeRadioButton =
await unDelegatePage.unMixNodeRadioButton.isSelected();
expect(mixNodeRadioButton).toEqual(false);
});
});
@@ -1,39 +0,0 @@
const walletLogin = require("../../pages/wallet.login");
const walletSignUp = require("../../pages/wallet.create");
const textConstants = require("../../../common/constants/text-constants");
describe("non existing wallet holder", () => {
//wallet mnemonic gets pushed here
const DATA = [];
it("create a new account and wallet", async () => {
const signInText = await walletLogin.signInLabel.getText();
expect(signInText).toEqual(textConstants.homePageSignIn);
await walletSignUp.createAccount.click();
//wallet generation takes some time - apply wait
await walletSignUp.create.click();
await walletSignUp.accountCreatedSuccessfully.waitForEnabled({
timeout: 10000,
});
const getWalletText = await walletSignUp.punkAddress.getText();
expect(getWalletText.length).toEqual(43);
const accountCreated =
await walletSignUp.accountCreatedSuccessfully.getText();
expect(accountCreated).toEqual(textConstants.walletSuccess);
const getMnemonic = await walletSignUp.walletMnemonicValue.getText();
DATA.push(getMnemonic);
});
it("navigate back to sign in screen and validate mnemonic works", async () => {
await walletSignUp.backToSignIn.click();
await walletLogin.enterMnemonic(DATA[0]);
await walletLogin.walletAddress.isDisplayed();
});
});
-93
View File
@@ -1,93 +0,0 @@
const os = require("os");
const path = require("path");
const { spawn, spawnSync } = require("child_process");
//insert path to binary
const nym_path = "../target/release/nym-wallet";
exports.config = {
//run sequentially, as using one default user may cause issues for parallel test runs for now
specs: [
"./tests/specs/existinguser/test.wallet.home.js",
"./tests/specs/existinguser/test.wallet.send.js",
"./tests/specs/existinguser/test.wallet.receive.js",
"./tests/specs/existinguser/test.wallet.bond.js",
"./tests/specs/existinguser/test.wallet.delegate.js",
"./tests/specs/newuser/test.wallet.create.js",
],
//run tests by providing --suite {{login}}
suites: {
home: ["./tests/specs/existinguser/test.wallet.home.js"],
sendreceive: [
"./tests/specs/existinguser/test.wallet.send.js",
"./tests/specs/existinguser/test.wallet.receive.js",
],
bond: ["./tests/specs/existinguser/test.wallet.bond.js"],
delegate: [
"./tests/specs/existinguser/test.wallet.delegate.js",
"./tests/specs/existinguser/test.wallet.undelegate.js",
],
newuser: ["./tests/specs/newuser/test.wallet.create.js"],
},
maxInstances: 1,
capabilities: [
{
maxInstances: 1,
"tauri:options": {
application: nym_path,
},
},
],
// ===================
// Test Configurations
// ===================
// Define all options that are relevant for the WebdriverIO instance here
// Level of logging verbosity: trace | debug | info | warn | error | silent
bail: 0,
framework: "mocha",
reporters: ["spec"],
mochaOpts: {
ui: "bdd",
timeout: 60000,
},
logLevel: "silent",
// ===================
// Test Reporters
// ===================
reporters: [
[
"allure",
{
outputDir: "allure-results",
disableWebdriverStepsReporting: true,
disableWebdriverScreenshotsReporting: true,
},
],
],
// this is documentented in the readme - you will need to build the project first
// ensure the rust project is built since we expect this binary to exist for the webdriver sessions
//onPrepare: () => spawnSync("cargo", ["build", "--release"]),
// ensure we are running `tauri-driver` before the session starts so that we can proxy the webdriver requests
beforeSession: () =>
(tauriDriver = spawn(
path.resolve(os.homedir(), ".cargo", "bin", "tauri-driver"),
[],
{ stdio: [null, process.stdout, process.stderr] }
)),
afterTest: function (
test,
context,
{ error, result, duration, passed, retries }
) {
if (error) {
browser.takeScreenshot();
}
},
// clean up the `tauri-driver` process we spawned at the start of the session
afterSession: () => tauriDriver.kill(),
};
File diff suppressed because it is too large Load Diff
+2
View File
@@ -6,6 +6,8 @@
"workspaces": [
"ts-packages/*",
"nym-wallet",
"nym-wallet/webdriver",
"nym-wallet/wallet-ui-tests",
"nym-connect",
"explorer",
"types",
@@ -27,7 +27,7 @@ export const CopyToClipboard: React.FC<{
{showConfirmation ? (
<DoneIcon color="success" sx={sx} />
) : (
<ContentCopyIcon onClick={handleCopy} sx={{ cursor: 'pointer', ...sx }} />
<ContentCopyIcon data-testid="ContentCopyIcon" onClick={handleCopy} sx={{ cursor: 'pointer', ...sx }} />
)}
</Tooltip>
);
@@ -81,6 +81,9 @@ export const IdentityKeyFormField: React.FC<{
return (
<TextField
fullWidth={fullWidth}
inputProps={{
"data-testid": placeholder
}}
InputProps={{
readOnly,
required,
+1450 -46
View File
File diff suppressed because it is too large Load Diff