Merge pull request #841 from nymtech/feature/tauri-wallet-tests

Feature NYM wallet webdriverio test
This commit is contained in:
Mark Sinclair
2021-10-26 10:21:45 +01:00
committed by GitHub
48 changed files with 3430 additions and 52 deletions
+8 -1
View File
@@ -7,12 +7,19 @@ SPDX-License-Identifier: Apache-2.0
A Rust and Tauri desktop wallet implementation.
## Installation prerequisites
## Installation prerequisites Linux / Mac
- `Yarn`
- `NodeJS >= v16.8.0`
- `Rust & cargo >= v1.51`
## Installation prerequisites Windows
- When running on Windows you will need to install c++ build tools
- An easy guide to get rust up and running [Installation]("http://kennykerr.ca/2019/11/18/rust-getting-started/")
- When installing NodeJS please use the `current features` version
- Using a package manager like [Chocolatey]("chocolatey.org") is recommended
## Installation
Inside of the `tauri-wallet` folder, run the following commands
+1 -1
View File
@@ -33,7 +33,7 @@ export const Confirmation = ({
{error === null ? (
SuccessMessage
) : (
<Alert severity="error" data-testid="errorMessage">
<Alert severity="error" data-testid="error-message">
<AlertTitle>{error.name}</AlertTitle>
<strong>{failureMessage}</strong> - {error.message}
</Alert>
@@ -40,7 +40,7 @@ export const CopyToClipboard = ({ text }: { text: string }) => {
size="small"
variant={copied ? 'text' : 'outlined'}
aria-label="save"
data-testid="copyButton"
data-testid="copy-button"
onClick={() => handleCopy({ text, cb: updateCopyStatus })}
endIcon={copied && <Check />}
style={copied ? { background: green[500], color: 'white' } : {}}
+2 -2
View File
@@ -6,11 +6,11 @@ import { Button } from '@material-ui/core'
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {
return (
<div>
<Alert severity="error" data-testid="errorMessage">
<Alert severity="error" data-testid="error-message">
<AlertTitle>{error.name}</AlertTitle>
{error.message}
</Alert>
<Alert severity="error" data-testid="stackTrace">
<Alert severity="error" data-testid="stack-trace">
<AlertTitle>Stack trace</AlertTitle>
{error.stack}
</Alert>
+1 -1
View File
@@ -131,7 +131,7 @@ export const Nav = () => {
)}
<ListItem button onClick={logOut}>
<ListItemIcon data-testid="logOut" className={classes.navItem}>
<ListItemIcon data-testid="log-out" className={classes.navItem}>
<ExitToApp />
</ListItemIcon>
<ListItemText
@@ -28,7 +28,7 @@ export const BalanceCard = () => {
noPadding
Action={
<Tooltip title="Refresh balance">
<IconButton data-testid="refreshBalance" onClick={getBalance.fetchBalance} size="small">
<IconButton data-testid="refresh-balance" onClick={getBalance.fetchBalance} size="small">
<Refresh />
</IconButton>
</Tooltip>
@@ -43,7 +43,7 @@ export const BalanceCard = () => {
{getBalance.error}
</Alert>
) : (
<Typography variant="h6">
<Typography variant="h6" data-testid="account-balance">
{getBalance.balance?.printable_balance}
</Typography>
)}
@@ -71,7 +71,7 @@ export const AddressCard = () => {
title="Address"
subheader="Wallet payments address"
noPadding
data-testid="walletAddressHeader"
data-testid="wallet-address-header"
Action={
<Tooltip title={!copyState ? 'Copy address' : 'Copied'}>
<span>
@@ -107,7 +107,7 @@ export const AddressCard = () => {
}
>
<CardContent>
<Typography data-testid="walletAddress"
<Typography data-testid="wallet-address"
style={{ fontWeight: theme.typography.fontWeightRegular }}
>
{truncate(clientDetails?.client_address!, 35)}
@@ -5,7 +5,7 @@ import { Alert, AlertTitle } from '@material-ui/lab'
export const NoClientError = () => {
return (
<Alert severity="error">
<AlertTitle data-testid="clientError">No client detected</AlertTitle>
<AlertTitle data-testid="client-error">No client detected</AlertTitle>
Have you signed in? Try to go back to{' '}
<Link to="/signin">the main page</Link> and try again
</Alert>
@@ -34,13 +34,13 @@ export const NodeTypeSelector = ({
value={EnumNodeType.mixnode}
control={<Radio />}
label="Mixnode"
data-testid="mixNode"
data-testid="mix-node"
disabled={disabled}
/>
<FormControlLabel
value={EnumNodeType.gateway}
control={<Radio />}
data-testid="gateWay"
data-testid="gate-way"
label="Gateway"
disabled={disabled}
/>
+1
View File
@@ -13,6 +13,7 @@ export const NymCard: React.FC<{
<CardHeader
title={title}
subheader={subheader}
data-testid={title}
titleTypographyProps={{ variant: 'h5' }}
subheaderTypographyProps={{ variant: 'subtitle1' }}
action={Action}
+4 -3
View File
@@ -18,6 +18,7 @@ export const Balance = () => {
size="small"
color="primary"
type="submit"
data-testid="refresh-button"
onClick={fetchBalance}
disabled={isLoading}
disableElevation
@@ -31,13 +32,13 @@ export const Balance = () => {
return (
<Layout>
<NymCard title="Check Balance" data-testid="checkBalance">
<NymCard title="Check Balance" data-testid="check-balance">
<Grid container direction="column" spacing={2}>
<Grid item>
{error && (
<Alert
severity="error"
data-testid="errorRefresh"
data-testid="error-refresh"
action={<RefreshAction />}
style={{ padding: theme.spacing(2) }}
>
@@ -47,7 +48,7 @@ export const Balance = () => {
{!error && (
<Alert
severity="success"
data-testid="refreshSuccess"
data-testid="refresh-success"
style={{ padding: theme.spacing(2, 3) }}
action={<RefreshAction />}
>
+2 -2
View File
@@ -142,7 +142,7 @@ export const BondForm = ({
</Grid>
{fees && (
<Grid item>
<Alert severity="info" data-testid="feeAmount">
<Alert severity="info" data-testid="fee-amount">
{`A fee of ${
watchNodeType === EnumNodeType.mixnode
? fees.mixnode.amount
@@ -369,7 +369,7 @@ export const BondForm = ({
color="primary"
type="submit"
size="large"
data-testid="submitButton"
data-testid="submit-button"
disableElevation
onClick={handleSubmit(onSubmit)}
endIcon={isSubmitting && <CircularProgress size={20} />}
+2 -2
View File
@@ -94,10 +94,10 @@ export const Bond = () => {
<RequestStatus
status={status}
Success={
<Alert severity="success" data-testid="bondSuccess">Successfully bonded node</Alert>
<Alert severity="success" data-testid="bond-success">Successfully bonded node</Alert>
}
Error={
<Alert severity="error" data-testid="bondError">
<Alert severity="error" data-testid="bond-error">
An error occurred with the request: {message}
</Alert>
}
@@ -97,7 +97,7 @@ export const DelegateForm = ({
/>
</Grid>
<Grid item>
<Alert severity="info" data-testid="feeAmount">
<Alert severity="info" data-testid="fee-amount">
{`A fee of ${
watchNodeType === EnumNodeType.mixnode
? fees.mixnode.amount
@@ -153,7 +153,7 @@ export const DelegateForm = ({
<Button
onClick={handleSubmit(onSubmit)}
disabled={isSubmitting}
data-testid="delegateButton"
data-testid="delegate-button"
variant="contained"
color="primary"
type="submit"
+3 -3
View File
@@ -73,13 +73,13 @@ export const Delegate = () => {
<RequestStatus
status={status}
Error={
<Alert severity="error" data-testid="delegateError">
<Alert severity="error" data-testid="delegate-error">
An error occurred with the request:
<Box style={{ wordBreak: 'break-word' }}>{message}</Box>
</Alert>
}
Success={
<Alert severity="success" data-testid="delegateSuccess">
<Alert severity="success" data-testid="delegate-success">
<AlertTitle>Delegation complete</AlertTitle>
{message}
</Alert>
@@ -96,7 +96,7 @@ export const Delegate = () => {
}}
>
<Button
data-testid="finishButton"
data-testid="finish-button"
onClick={() => {
setStatus(EnumRequestStatus.initial)
}}
+3 -3
View File
@@ -17,7 +17,7 @@ export const Receive = () => {
<NymCard title="Receive Nym">
<Grid container direction="column" spacing={1}>
<Grid item>
<Alert severity="info" data-testid="receiveNym">
<Alert severity="info" data-testid="receive-nym">
You can receive tokens by providing this address to the sender
</Alert>
</Grid>
@@ -40,7 +40,7 @@ export const Receive = () => {
>
<Grid item>
<Typography
data-testid="clientAddress"
data-testid="client-address"
variant={matches ? 'h5' : 'subtitle1'}
style={{
wordBreak: 'break-word',
@@ -62,7 +62,7 @@ export const Receive = () => {
component="div"
>
{clientDetails && (
<QRCode data-testid="qrCode" value={clientDetails.client_address} />
<QRCode data-testid="qr-code" value={clientDetails.client_address} />
)}
</Box>
</Grid>
@@ -46,7 +46,7 @@ export const SendConfirmation = ({
marginBottom: theme.spacing(1),
}}
/>
<Typography data-testid="transactionComplete">Transaction complete</Typography>
<Typography data-testid="transaction-complete">Transaction complete</Typography>
</div>
<Card
@@ -60,7 +60,7 @@ export const SendConfirmation = ({
</Typography>
</div>
<div style={{ wordBreak: 'break-all' }}>
<Typography data-testid="toAddress">{data.to_address}</Typography>
<Typography data-testid="to-address">{data.to_address}</Typography>
</div>
</div>
<div style={{ display: 'flex' }}>
@@ -70,7 +70,7 @@ export const SendConfirmation = ({
</Typography>
</div>
<div>
<Typography data-testid="sendAmount">{data.amount.amount + ' punks'}</Typography>
<Typography data-testid="send-amount">{data.amount.amount + ' punks'}</Typography>
</div>
</div>
</Card>
+1 -1
View File
@@ -37,7 +37,7 @@ export const SendError = ({ message }: { message?: string }) => {
variant="outlined"
style={{ width: '100%', padding: theme.spacing(2) }}
>
<Alert severity="error" data-testid="transactioError">
<Alert severity="error" data-testid="transaction-error">
An error occured during the request {message}
</Alert>
</Card>
+4 -5
View File
@@ -44,19 +44,19 @@ export const SendReview = () => {
) : (
<Grid container spacing={2}>
<Grid item xs={12}>
<SendReviewField title="From" subtitle={values.from} data-testid="fromAddress" />
<SendReviewField title="From" subtitle={values.from}/>
</Grid>
<Grid item xs={12}>
<Divider light />
</Grid>
<Grid item xs={12}>
<SendReviewField title="To" subtitle={values.to} data-testid="toAddress"/>
<SendReviewField title="To" subtitle={values.to} />
</Grid>
<Grid item xs={12}>
<Divider light />
</Grid>
<Grid item xs={12}>
<SendReviewField title="Amount" subtitle={values.amount} data-testid="transferAmount"/>
<SendReviewField title="Amount" subtitle={values.amount} />
</Grid>
<Grid item xs={12}>
<Divider light />
@@ -65,7 +65,6 @@ export const SendReview = () => {
<SendReviewField
title="Transfer fee"
subtitle={transferFee + ' PUNK'}
data-testid="transferFee"
/>
</Grid>
</Grid>
@@ -87,7 +86,7 @@ export const SendReviewField = ({
<Typography style={{ color: theme.palette.grey[600] }}>
{title}
</Typography>
<Typography style={{ wordBreak: 'break-all' }}>{subtitle}</Typography>
<Typography data-testid={title} style={{ wordBreak: 'break-all' }}>{subtitle}</Typography>
</>
)
}
+1 -1
View File
@@ -148,7 +148,7 @@ export const SendWizard = () => {
disableElevation
style={{ marginRight: theme.spacing(1) }}
onClick={handlePreviousStep}
data-testid="backButton"
data-testid="back-button"
>
Back
</Button>
+6 -6
View File
@@ -100,7 +100,7 @@ const SignInContent = ({
return (
<SignInCard>
<>
<Typography variant="h4">Sign in</Typography>
<Typography variant="h4" data-testid="sign-in">Sign in</Typography>
<form noValidate onSubmit={handleSignIn}>
<Grid container direction="column" spacing={1}>
<Grid item>
@@ -239,7 +239,7 @@ const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
/>
<Typography>Wallet setup complete</Typography>
</div>
<Alert severity="info" style={{ marginBottom: theme.spacing(2) }} data-testid="mnemonicWarning">
<Alert severity="info" style={{ marginBottom: theme.spacing(2) }} data-testid="mnemonic-warning">
Please store your <strong>mnemonic</strong> in a safe place.
You'll need it to access your wallet
</Alert>
@@ -257,7 +257,7 @@ const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
</Typography>
</Grid>
<Grid item>
<Typography data-testid="mnemonicPhrase">{accountDetails.mnemonic}</Typography>
<Typography data-testid="mnemonic-phrase">{accountDetails.mnemonic}</Typography>
<div
style={{ display: 'flex', justifyContent: 'flex-end' }}
>
@@ -273,7 +273,7 @@ const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
</Typography>
</Grid>
<Grid item>
<Typography data-testid="walletAdress">{accountDetails.client_address}</Typography>
<Typography data-testid="wallet-address">{accountDetails.client_address}</Typography>
</Grid>
</Grid>
</Card>
@@ -293,7 +293,7 @@ const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
variant="contained"
color="primary"
type="submit"
data-testid="createButton"
data-testid="create-button"
disableElevation
style={{ marginBottom: theme.spacing(1) }}
disabled={isLoading}
@@ -305,7 +305,7 @@ const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
fullWidth
variant="text"
onClick={showSignIn}
data-testid="signInButton"
data-testid="sign-in-button"
startIcon={<ArrowBack />}
>
Sign in
+3 -3
View File
@@ -29,10 +29,10 @@ export const Unbond = () => {
{ownership?.hasOwnership && (
<Alert
severity="warning"
data-testid="bondNoded"
data-testid="bond-noded"
action={
<Button
data-testid="unBond"
data-testid="un-bond"
disabled={isLoading}
onClick={async () => {
setIsLoading(true)
@@ -50,7 +50,7 @@ export const Unbond = () => {
</Alert>
)}
{!ownership.hasOwnership && (
<Alert severity="info" style={{ margin: theme.spacing(3) }} data-testid="noBond">
<Alert severity="info" style={{ margin: theme.spacing(3) }} data-testid="no-bond">
You don't currently have a bonded node
</Alert>
)}
@@ -84,7 +84,7 @@ export const UndelegateForm = ({
/>
</Grid>
<Grid item>
<Alert severity="info" data-testid="feeAmount">
<Alert severity="info" data-testid="fee-amount">
{`A fee of ${
watchNodeType === EnumNodeType.mixnode
? fees.mixnode.amount
@@ -140,7 +140,7 @@ export const UndelegateForm = ({
variant="contained"
color="primary"
type="submit"
data-testid="submitButton"
data-testid="submit-button"
disableElevation
disabled={isSubmitting}
endIcon={isSubmitting && <CircularProgress size={20} />}
+3 -3
View File
@@ -102,14 +102,14 @@ export const Undelegate = () => {
<RequestStatus
status={status}
Error={
<Alert severity="error" data-testid="requestError">
<Alert severity="error" data-testid="request-error">
An error occurred with the request: {message}
</Alert>
}
Success={
<Alert severity="success">
{' '}
<AlertTitle data-testid="undelegateSuccess">Undelegation complete</AlertTitle>
<AlertTitle data-testid="undelegate-success">Undelegation complete</AlertTitle>
{message}
</Alert>
}
@@ -125,7 +125,7 @@ export const Undelegate = () => {
}}
>
<Button
data-testid="finishButton"
data-testid="finish-button"
onClick={() => {
setStatus(EnumRequestStatus.initial)
initialize()
+5
View File
@@ -0,0 +1,5 @@
reports
allure-results
node_modules
.vscode
.idea
+79
View File
@@ -0,0 +1,79 @@
<!--
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.51`
* `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
@@ -0,0 +1,12 @@
module.exports = {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "14"
}
}
]
]
}
@@ -0,0 +1,28 @@
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 or gateway",
"nodeIdentityValidationText" : "identity is a required field",
"amountValidationText": "amount is a required field",
"undelegateHeaderText" : "Undelegate from a mixnode or gateway",
"delegationComplete" : "Delegation complete"
}
@@ -0,0 +1,9 @@
{
"mnemonic": "value",
"punk_address": "",
"receiver_address": "",
"amount_to_send": "",
"identity_key_to_delegate_mix_node": "",
"identity_key_to_delegate_gateway" : "",
"delegate_amount": ""
}
@@ -0,0 +1,45 @@
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()
+25
View File
@@ -0,0 +1,25 @@
{
"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"
},
"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"
}
}
@@ -0,0 +1,20 @@
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()
@@ -0,0 +1,12 @@
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()
@@ -0,0 +1,26 @@
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()
@@ -0,0 +1,18 @@
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()
@@ -0,0 +1,18 @@
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()
@@ -0,0 +1,24 @@
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()
@@ -0,0 +1,22 @@
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()
@@ -0,0 +1,12 @@
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()
@@ -0,0 +1,55 @@
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)
})
})
@@ -0,0 +1,90 @@
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)
})
})
@@ -0,0 +1,51 @@
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)
})
})
@@ -0,0 +1,29 @@
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)
})
})
@@ -0,0 +1,55 @@
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)
})
})
@@ -0,0 +1,32 @@
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)
})
})
@@ -0,0 +1,38 @@
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()
})
})
+80
View File
@@ -0,0 +1,80 @@
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
+1
View File
@@ -195,6 +195,7 @@ export default function SendFunds() {
variant="contained"
color="primary"
type="submit"
data-testid="button"
disabled={checkButtonDisabled()}
>
{activeStep === 1