Fix/trading app tests (#539)

* fix: deposits tests, also convert to basic cypress

* add new home tests which test redirect to trading page and markets page

* chore: replace portfolio page feature with raw cypress

* chore: replace market page feature with raw cypress tests

* chore: replace home page tests with global.ts for wallet connections

* chore: add raw cypress withdrawals tests with mocks

* fix: complete withdrawals prompt and add assertion for it

* chore: remove unnecessary cypress envs now that we are mocking assets

* chore: ignore lint errors temporarily

* chore: add mock for deposit page query, add wait for mocked queries to resolve

* fix: order of waiting for withdraw page query

* fix: validate vega wallet connection

* chore: remove rest of page objects and convert trading page feature to regular cypress

* fix: assertion on transaction dialog after withdrawal

* chore: split withdraw and withdrawals pages into separate files

* chore: split trading tests into own files, connect wallet once for deal ticket

* feat: convert home page tests to raw cypress
This commit is contained in:
Matthew Russell 2022-06-10 12:00:02 -07:00 committed by GitHub
parent 72f94d2e6d
commit 9941c9bfaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 1259 additions and 2021 deletions

View File

@ -23,10 +23,8 @@
"VEGA_PUBLIC_KEY2": "1a18cdcaaa4f44a57b35a4e9b77e0701c17a476f2b407620f8c17371740cf2e4",
"TRUNCATED_VEGA_PUBLIC_KEY": "47836c…c7d278",
"TRUNCATED_VEGA_PUBLIC_KEY2": "1a18cd…0cf2e4",
"INVALID_DEPOSIT_TO_ADDRESS": "zzz85edfa7ffdb6ed996ca912e9258998e47bf3515c885cf3c63fb56b15de36f",
"ETHEREUM_WALLET_ADDRESS": "0x265Cc6d39a1B53d0d92068443009eE7410807158",
"ETHERSCAN_URL": "https://ropsten.etherscan.io",
"WITHDRAWAL_ASSET_ID": "tEURO TEST",
"tsConfig": "tsconfig.json",
"TAGS": "not @todo and not @ignore and not @manual"
}

View File

@ -0,0 +1,60 @@
import { hasOperationName } from '../support';
import { generateDepositPage } from '../support/mocks/generate-deposit-page';
describe('deposit form validation', () => {
beforeEach(() => {
cy.mockWeb3Provider();
cy.mockGQL('DepositPage', (req) => {
if (hasOperationName(req, 'DepositPage')) {
req.reply({
body: { data: generateDepositPage() },
});
}
});
cy.visit('/portfolio/deposit');
// Deposit page requires connection Ethereum wallet first
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-MetaMask').click();
cy.wait('@DepositPage');
cy.contains('Deposit');
});
it('handles empty fields', () => {
const assetSelectField = 'select[name="asset"]';
const toAddressField = 'input[name="to"]';
const amountField = 'input[name="amount"]';
const formFieldError = 'input-error-text';
// Submit form to trigger any empty validaion messages
cy.getByTestId('deposit-submit').click();
cy.getByTestId(formFieldError).should('contain.text', 'Required');
cy.getByTestId(formFieldError).should('have.length', 3);
// Invalid public key
cy.get(toAddressField)
.clear()
.type('INVALID_DEPOSIT_TO_ADDRESS')
.next(`[data-testid="${formFieldError}"]`)
.should('have.text', 'Invalid Vega key');
// Deposit amount smaller than minimum viable for selected asset
// Select an amount so that we have a known decimal places value to work with
cy.get(assetSelectField).select('Asset 0');
cy.get(amountField)
.clear()
.type('0.00000000000000000000000000000000001')
.next(`[data-testid="${formFieldError}"]`)
.should('have.text', 'Value is below minimum');
// Deposit amount is valid, but less than approved. This will always be the case because our
// CI wallet wont have approved any assets
cy.get(amountField)
.clear()
.type('100')
.next(`[data-testid="${formFieldError}"]`)
.should('have.text', 'Amount is above approved amount');
});
});

View File

@ -1,154 +0,0 @@
Feature: Deposits to vega wallet
Background:
Given I can connect to Ethereum
And I navigate to deposits page
# wallet is already connected before tests start and doesn't prompt the disconnected state
@ignore
Scenario: Connecting Ethereum wallet
Then I can see the eth not connected message "Connect your Ethereum wallet"
And the connect button is displayed
When I connect my Ethereum wallet
Then I can see the deposit form
@todo
Scenario: Cannot deposit if approved amount is 0 (approval amount is 0)
And I connect my Ethereum wallet
When I set "0" tokens to be approved
And I approve the asset tokens
And I can see the deposit form is displayed
And I select "" asset from the dropdown list
When I enter the following details
| Field | Value |
| To (Vega key) | xxxxxxxx |
| Amount | 50 |
And I click to the deposit the funds
And I approve the ethereum transaction
# The following step is valid and are commented out as currently it cannot be automated
# Then I can see the deposit is unsuccessful
@todo
Scenario: Cannot deposit if approved amount is lower than deposit amount
When I set "2" tokens to be approved
And I approve the asset tokens
And I can see the deposit form is displayed
And I select "" asset from the dropdown list
When I enter the following details
| Field | Value |
| To (Vega key) | xxxxxxxx |
| Amount | 50 |
And I click to the deposit the funds
And I approve the ethereum transaction
# The following step is valid and are commented out as currently it cannot be automated
# Then I can see the deposit is unsuccessful
@todo
Scenario: Can succesfully deposit (approved amount is greater than deposit)
When I set "200000000" tokens to be approved
And I approve the asset tokens
And I can see the deposit form is displayed
And I select "" asset from the dropdown list
When I enter the following details
| Field | Value |
| To (Vega key) | xxxxxxxx |
| Amount | 50 |
And I click to the deposit the funds
And I approve the ethereum transaction
# The following steps are valid and are commented out as currently they cannot be automated
# Then I can see the deposit is Successfull
# And Balance is updated to reflect deposit amount
Scenario: Empty form Validation errors
When I submit a deposit with empty fields
Then I can see empty form validation errors present
Scenario: Invalid deposit public key validation error
When I enter the following deposit details in deposit form
| asset | tBTC TEST |
| to | INVALID_DEPOSIT_TO_ADDRESS |
| amount | 1 |
And I submit the form
Then Invalid Vega key is shown
Scenario: Deposit amount too small validation
When I enter the following deposit details in deposit form
| asset | tBTC TEST |
| to | INVALID_DEPOSIT_TO_ADDRESS |
| amount | 0.00000000000000000000000000000000001 |
And I submit the form
Then Amount too small message shown
@ignore
Scenario: Deposit amount greater than approved amount validation
When I enter the following deposit details in deposit form
| asset | tBTC TEST |
| to | INVALID_DEPOSIT_TO_ADDRESS |
| amount | 788888888888888 |
And I submit the form
And Insufficient amount message shown
# Then Amount too small message shown
# And I enter a valid amount
# And I submit the form
# This next step is being skipped due to account having approved status
# Then Not approved message shown
@ignore
Scenario: Successful deposit
When I enter the following deposit details in deposit form
| asset | tBTC TEST |
| to | VEGA_PUBLIC_KEY |
| amount | 1 |
And I submit the form
And I can see the 'deposit pending' modal is shown
@todo
Scenario: Use the 'Use Maximum' button to populate amount input with the balance in the connected wallet
And I can see the deposit form is displayed
And I select "" asset from the dropdown list
When I enter the following details
| Field | Value |
| To (Vega key) | xxxxxxxx |
| Amount | 0 |
When I click the use maximum button
Then I can see the field is updated with the maximum amount of the asset from my wallet
@todo
Scenario: User is warned if the the amount to deposit is greater than what is available in the connected wallet"
And I can see the deposit form is displayed
And I select "" asset from the dropdown list
When I enter the following details
| Field | Value |
| To (Vega key) | xxxxxxxx |
| Amount | 60000000 |
And I click to the deposit the funds
Then an error message is shown stating not enough tokens in wallet to deposit
@todo
Scenario: Deposit to a vega wallet key which is not your own
And I can see the deposit form is displayed
And I select "" asset from the dropdown list
When I enter the following details
| Field | Value |
| To (Vega key) | VEGA KEY of another wallet |
| Amount | 50 |
And I click to the deposit the funds
And I approve the ethereum transaction
# The following steps are valid and are commented out as currently they cannot be automated
# Then I can see the deposit is Successfull
# And Balance is updated to reflect deposit amount
@todo
Scenario: Deposit when vega wallet is not connected
And I disconnect my vega wallet
And I can see the deposit form is displayed
And I select "" asset from the dropdown list
When I enter the following details
| Field | Value |
| To (Vega key) | xxxxxxxx |
| Amount | 50 |
And I click to the deposit the funds
And I approve the ethereum transaction
# The following step is valid and are commented out as currently it cannot be automated
# Then I can see the deposit is unsuccessful

View File

@ -0,0 +1,78 @@
import { connectVegaWallet } from '../support/vega-wallet';
describe('vega wallet', () => {
const connectVegaBtn = 'connect-vega-wallet';
const manageVegaBtn = 'manage-vega-wallet';
const form = 'rest-connector-form';
const walletName = Cypress.env('TRADING_TEST_VEGA_WALLET_NAME');
const walletPassphrase = Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE');
beforeEach(() => {
// Using portfolio page as it requires vega wallet connection
cy.visit('/portfolio');
});
it('can connect', () => {
cy.getByTestId(connectVegaBtn).click();
cy.contains('Connects using REST to a running Vega wallet service');
cy.getByTestId('connectors-list').find('button').click();
cy.getByTestId(form).find('#wallet').click().type(walletName);
cy.getByTestId(form).find('#passphrase').click().type(walletPassphrase);
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId(manageVegaBtn).should('exist');
});
it('doesnt connect with invalid credentials', () => {
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list').find('button').click();
cy.getByTestId(form).find('#wallet').click().type('invalid name');
cy.getByTestId(form).find('#passphrase').click().type('invalid password');
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId('form-error').should('have.text', 'Authentication failed');
});
it('doesnt connect with invalid fields', () => {
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list').find('button').click();
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId(form)
.find('#wallet')
.next('[data-testid="input-error-text"]')
.should('have.text', 'Required');
cy.getByTestId(form)
.find('#passphrase')
.next('[data-testid="input-error-text"]')
.should('have.text', 'Required');
});
it('can change selected public key and disconnect', () => {
connectVegaWallet();
cy.getByTestId('manage-vega-wallet').click();
cy.getByTestId('keypair-list').should('exist');
cy.getByTestId('select-keypair-button').click();
cy.getByTestId('keypair-list').should('not.exist');
cy.getByTestId('manage-vega-wallet').contains(
Cypress.env('TRUNCATED_VEGA_PUBLIC_KEY2')
);
cy.getByTestId('manage-vega-wallet').click();
cy.getByTestId('disconnect').click();
cy.getByTestId('connect-vega-wallet').should('exist');
cy.getByTestId('manage-vega-wallet').should('not.exist');
});
});
describe('ethereum wallet', () => {
beforeEach(() => {
cy.mockWeb3Provider();
// Using portfolio is it requires Ethereum wallet connection
cy.visit('/portfolio');
});
it('can connect', () => {
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-MetaMask').click();
cy.getByTestId('web3-connector-list').should('not.exist');
cy.getByTestId('portfolio-grid').should('exist');
});
});

View File

@ -1,67 +0,0 @@
Feature: Home page
Background:
Given I am on the homepage
And I query the server for Open Markets
When the server contains at least 6 open markets
Scenario: Choose market overlay: prompted to choose market
Then I am prompted to select a market
Scenario: Choose market overlay: oldest currently trading market shown in background by default
Then the oldest current trading market is loaded on the trading tab
Scenario: Choose market overlay: contains at least 6 listed markets
Then I expect the market overlay table to contain at least 6 rows
Scenario: Choose market overlay: each listed item reflects an open market
Then each market shown in overlay table exists as open market on server
Scenario: Choose market overlay: each listed item contains values for last price and change
Then each market shown in overlay table contains content under the last price and change fields
Scenario: Choose market overlay: oldest trading market appears at top of list
Then the oldest market trading in continous mode shown at top of overlay table
Scenario: Choose market overlay: can be closed without choosing an option
When I close the dialog form
And the choose market overlay is no longer showing
Then the oldest current trading market is loaded on the trading tab
Scenario: Choose market overlay: clicking a market name will load that market
When I click the most recent trading market
And the choose market overlay is no longer showing
Then the most recent current trading market is loaded on the trading tab
Scenario: Choose market overlay: full market list: clicking the full market list shows all open markets
When I click the view full market list
And the choose market overlay is no longer showing
Then each market shown in the full list exists as open market on server
Scenario: Choose market overlay: full market list: clicking a market name will load that market
When I click the view full market list
And the choose market overlay is no longer showing
When I click the most recent trading market
Then the most recent current trading market is loaded on the trading tab
Scenario: Navigation: Visit Portfolio page
When I close the dialog form
And I navigate to portfolio page
Scenario: Navigation: Visit Markets page
When I close the dialog form
And I navigate to markets page
Scenario: Vega Wallett Overlay: Able to switch public key for connected Vega wallet
Given I connect to Vega Wallet
When I open wallet dialog
And select a different public key
Then public key is switched
Scenario: Vega Wallett Overlay: Unable to connect Vega wallet with incorrect credentials
When I try to connect Vega wallet with incorrect details
Then wallet not running error message is displayed
Scenario: Vega Wallett Overlay: Unable to connect Vega wallet with blank fields
When I try to connect Vega wallet with blank fields
Then wallet field validation errors are shown

View File

@ -0,0 +1,171 @@
import type { MarketList, MarketList_markets } from '@vegaprotocol/market-list';
import { MarketState } from '@vegaprotocol/types';
import { hasOperationName } from '../support';
import { generateMarketList } from '../support/mocks/generate-market-list';
import { generateMarkets } from '../support/mocks/generate-markets';
import type { MarketsLanding } from '../support/mocks/generate-markets-landing';
import { generateMarketsLanding } from '../support/mocks/generate-markets-landing';
import { mockTradingPage } from '../support/trading';
describe('home', () => {
const selectMarketOverlay = 'select-market-list';
describe('default market found', () => {
let marketsLanding: MarketsLanding;
let marketList: MarketList;
let oldestMarket: MarketList_markets;
beforeEach(() => {
marketsLanding = generateMarketsLanding();
marketList = generateMarketList();
oldestMarket = getOldestOpenMarket(
marketList.markets as MarketList_markets[]
);
// Mock markets query that is triggered by home page to find default market
cy.mockGQL('MarketsLanding', (req) => {
if (hasOperationName(req, 'MarketsLanding')) {
req.reply({
body: { data: marketsLanding },
});
}
});
// Market market list for the dialog that opens on a trading page
cy.mockGQL('MarketList', (req) => {
if (hasOperationName(req, 'MarketList')) {
req.reply({
body: { data: marketList },
});
}
});
// Mock market page
mockTradingPage(MarketState.Active);
cy.visit('/');
cy.wait('@MarketsLanding');
cy.url().should('include', `/markets/${oldestMarket.id}`); // Should redirect to oldest market
cy.wait('@MarketList');
});
it('redirects to a default market with the landing dialog open', () => {
// Overlay should be shown
cy.getByTestId(selectMarketOverlay).should('exist');
cy.contains('Select a market to get started').should('be.visible');
// I expect the market overlay table to contain at least 3 rows (one header row)
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.then((row) => {
expect(row.length).to.eq(3);
});
// each market shown in overlay table contains content under the last price and change fields
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.each(($element, index) => {
if (index > 0) {
// skip header row
cy.root().within(() => {
cy.getByTestId('price').should('not.be.empty');
});
}
});
// the oldest market trading in continous mode shown at top of overlay table
cy.get('table tr')
.eq(1)
.within(() =>
cy
.contains(oldestMarket.tradableInstrument.instrument.code)
.should('be.visible')
);
cy.getByTestId('dialog-close').click();
cy.getByTestId(selectMarketOverlay).should('not.exist');
// the choose market overlay is no longer showing
cy.contains('Select a market to get started').should('not.exist');
cy.contains('Loading...').should('not.exist');
});
it('can click a market name to load that market', () => {
// Click newer market
cy.getByTestId(selectMarketOverlay)
.should('exist')
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.contains(marketList.markets![1].tradableInstrument.instrument.code)
.click();
cy.getByTestId(selectMarketOverlay).should('not.exist');
cy.url().should('include', 'market-1');
});
it('view full market list goes to markets page', () => {
cy.getByTestId(selectMarketOverlay)
.should('exist')
.contains('Or view full market list')
.click();
cy.getByTestId(selectMarketOverlay).should('not.exist');
cy.url().should('include', '/markets');
cy.get('main[data-testid="markets"]').should('exist');
});
});
describe('no default found', () => {
it('redirects to a the market list page if no sensible default is found', () => {
// Mock markets query that is triggered by home page to find default market
cy.mockGQL('MarketsLanding', (req) => {
if (hasOperationName(req, 'MarketsLanding')) {
req.reply({
body: {
// Remove open timestamps so we can't calculate a sensible default market
data: generateMarketsLanding({
markets: [
{
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '',
},
},
{
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '',
},
},
],
}),
},
});
}
});
cy.mockGQL('Markets', (req) => {
if (hasOperationName(req, 'Markets')) {
req.reply({
body: {
data: generateMarkets(),
},
});
}
});
cy.visit('/');
cy.wait('@MarketsLanding');
cy.url().should('include', '/markets');
});
});
});
function getOldestOpenMarket(openMarkets: MarketList_markets[]) {
const [oldestMarket] = openMarkets.sort(
(a, b) =>
new Date(a.marketTimestamps.open as string).getTime() -
new Date(b.marketTimestamps.open as string).getTime()
);
if (!oldestMarket) {
throw new Error('Could not find oldest market');
}
return oldestMarket;
}

View File

@ -1,19 +0,0 @@
Feature: Markets page
Scenario: Navigation
Given I am on the homepage
When I navigate to markets page
Then I can view markets
And the market table is displayed
Scenario: Select active market
Given I am on the markets page
And I can view markets
When I click on "Active" mocked market
Then trading page for "active" market is displayed
Scenario: Select suspended market
Given I am on the markets page
And I can view markets
When I click on "Suspended" mocked market
Then trading page for "suspended" market is displayed

View File

@ -0,0 +1,92 @@
import { MarketState } from '@vegaprotocol/types';
import { hasOperationName } from '../support';
import { generateMarkets } from '../support/mocks/generate-markets';
import { mockTradingPage } from '../support/trading';
describe('markets table', () => {
beforeEach(() => {
cy.mockGQL('Markets', (req) => {
if (hasOperationName(req, 'Markets')) {
req.reply({
body: { data: generateMarkets() },
});
}
});
cy.visit('/markets');
});
it('renders correctly', () => {
const marketRowHeaderClassname = 'div > span.ag-header-cell-text';
const marketRowNameColumn = 'tradableInstrument.instrument.code';
const marketRowSymbolColumn =
'tradableInstrument.instrument.product.settlementAsset.symbol';
const marketRowPrices = 'flash-cell';
const marketRowDescription = 'name';
cy.wait('@Markets');
cy.get('.ag-root-wrapper').should('be.visible');
const expectedMarketHeaders = [
'Market',
'Settlement asset',
'State',
'Best bid',
'Best offer',
'Mark price',
'Description',
];
for (let index = 0; index < expectedMarketHeaders.length; index++) {
cy.get(marketRowHeaderClassname).should(
'contain.text',
expectedMarketHeaders[index]
);
}
cy.get(`[col-id='${marketRowNameColumn}']`).each(($marketName) => {
cy.wrap($marketName).should('not.be.empty');
});
cy.get(`[col-id='${marketRowSymbolColumn}']`).each(($marketSymbol) => {
cy.wrap($marketSymbol).should('not.be.empty');
});
cy.getByTestId(marketRowPrices).each(($price) => {
cy.wrap($price).should('not.be.empty').and('contain.text', '.');
});
cy.get(`[col-id='${marketRowDescription}']`).each(($marketDescription) => {
cy.wrap($marketDescription).should('not.be.empty');
});
});
it('can select an active market', () => {
cy.wait('@Markets');
cy.get('.ag-root-wrapper').should('be.visible');
mockTradingPage(MarketState.Active);
// click on active market
cy.get('[role="gridcell"][col-id=data]').should('be.visible');
cy.get('[role="gridcell"][col-id=data]').contains('Active').click();
cy.wait('@Market');
cy.contains('ACTIVE MARKET');
cy.url().should('include', '/markets/market-0');
});
it('can select a suspended market', () => {
cy.wait('@Markets');
cy.get('.ag-root-wrapper').should('be.visible');
mockTradingPage(MarketState.Suspended);
// click on active market
cy.get('[role="gridcell"][col-id=data]').should('be.visible');
cy.get('[role="gridcell"][col-id=data]').contains('Suspended').click();
cy.wait('@Market');
cy.contains('SUSPENDED MARKET');
cy.url().should('include', '/markets/market-1');
});
});

View File

@ -1,5 +0,0 @@
Feature: Portfolio page
Scenario: Navigation
Given I am on the homepage
Then I navigate to portfolio page

View File

@ -0,0 +1,6 @@
describe('portfolio', () => {
it('requires connecting', () => {
cy.visit('/portfolio');
cy.get('main[data-testid="portfolio"]').should('exist');
});
});

View File

@ -0,0 +1,38 @@
import { MarketState } from '@vegaprotocol/types';
import { mockTradingPage } from '../support/trading';
import { connectVegaWallet } from '../support/vega-wallet';
beforeEach(() => {
mockTradingPage(MarketState.Active);
cy.visit('/markets/market-0');
});
describe('accounts', () => {
it('renders accounts', () => {
cy.getByTestId('Accounts').click();
cy.getByTestId('tab-accounts').contains('Please connect Vega wallet');
connectVegaWallet();
cy.getByTestId('tab-accounts').should('be.visible');
cy.getByTestId('tab-accounts')
.get(`[row-id='General-tEURO-null']`)
.find('[col-id="asset.symbol"]')
.should('have.text', 'tEURO');
cy.getByTestId('tab-accounts')
.get(`[row-id='General-tEURO-null']`)
.find('[col-id="type"]')
.should('have.text', 'General');
cy.getByTestId('tab-accounts')
.get(`[row-id='General-tEURO-null']`)
.find('[col-id="market.name"]')
.should('have.text', '—');
cy.getByTestId('tab-accounts')
.get(`[row-id='General-tEURO-null']`)
.find('[col-id="balance"]')
.should('have.text', '1,000.00000');
});
});

View File

@ -0,0 +1,166 @@
import { MarketState } from '@vegaprotocol/types';
import { mockTradingPage } from '../support/trading';
import { connectVegaWallet } from '../support/vega-wallet';
interface Order {
type: 'TYPE_MARKET' | 'TYPE_LIMIT';
side: 'SIDE_BUY' | 'SIDE_SELL';
size: string;
price?: string;
timeInForce:
| 'TIME_IN_FORCE_GTT'
| 'TIME_IN_FORCE_GTC'
| 'TIME_IN_FORCE_IOC'
| 'TIME_IN_FORCE_FOK'
| 'TIME_IN_FORCE_GFN'
| 'TIME_IN_FORCE_GFA';
expiresAt?: string;
}
describe('deal ticket orders', () => {
const orderSizeField = 'order-size';
const orderPriceField = 'order-price';
const orderTIFDropDown = 'order-tif';
const placeOrderBtn = 'place-order';
const orderStatusHeader = 'order-status-header';
const orderTransactionHash = 'tx-hash';
before(() => {
mockTradingPage(MarketState.Active);
cy.visit('/markets/market-0');
connectVegaWallet();
});
beforeEach(() => {
cy.mockVegaCommandSync({
txHash: 'test-tx-hash',
tx: {
signature: {
value:
'd86138bba739bbc1069b3dc975d20b3a1517c2b9bdd401c70eeb1a0ecbc502ec268cf3129824841178b8b506b0b7d650c76644dbd96f524a6cb2158fb7121800',
},
},
});
});
it('successfully places market buy order', () => {
const order: Order = {
type: 'TYPE_MARKET',
side: 'SIDE_BUY',
size: '100',
timeInForce: 'TIME_IN_FORCE_FOK',
};
testOrder(order);
});
it('successfully places market sell order', () => {
const order: Order = {
type: 'TYPE_MARKET',
side: 'SIDE_SELL',
size: '100',
timeInForce: 'TIME_IN_FORCE_IOC',
};
testOrder(order);
});
it('successfully places limit buy order', () => {
const order: Order = {
type: 'TYPE_LIMIT',
side: 'SIDE_BUY',
size: '100',
price: '200',
timeInForce: 'TIME_IN_FORCE_GTC',
};
testOrder(order, { price: '20000' });
});
it('successfully places limit sell order', () => {
const order: Order = {
type: 'TYPE_LIMIT',
side: 'SIDE_SELL',
size: '100',
price: '50000',
timeInForce: 'TIME_IN_FORCE_GFN',
};
testOrder(order, { price: '5000000' });
});
it('successfully places GTT limit buy order', () => {
const order: Order = {
type: 'TYPE_LIMIT',
side: 'SIDE_SELL',
size: '100',
price: '1.00',
timeInForce: 'TIME_IN_FORCE_GTT',
expiresAt: '2022-01-01T00:00',
};
testOrder(order, {
price: '100',
expiresAt:
new Date(order.expiresAt as string).getTime().toString() + '000000',
});
});
const testOrder = (order: Order, expected?: Partial<Order>) => {
const { type, side, size, price, timeInForce, expiresAt } = order;
cy.get(`[name="order-type"][value="${type}"`).click({ force: true }); // force as input is hidden and displayed as a button
cy.get(`[name="order-side"][value="${side}"`).click({ force: true });
cy.getByTestId(orderSizeField).clear().type(size);
if (price) {
cy.getByTestId(orderPriceField).clear().type(price);
}
cy.getByTestId(orderTIFDropDown).select(timeInForce);
if (timeInForce === 'TIME_IN_FORCE_GTT') {
if (!expiresAt) {
throw new Error('Specify expiresAt if using GTT');
}
// select expiry
cy.getByTestId('date-picker-field').type(expiresAt);
}
cy.getByTestId(placeOrderBtn).click();
const expectedOrder = {
...order,
...expected,
};
cy.wait('@VegaCommandSync')
.its('request.body')
.should('deep.equal', {
pubKey: Cypress.env('VEGA_PUBLIC_KEY'),
propagate: true,
orderSubmission: {
marketId: 'market-0',
...expectedOrder,
},
});
cy.getByTestId(orderStatusHeader).should(
'have.text',
'Awaiting network confirmation'
);
cy.getByTestId(orderTransactionHash)
.invoke('text')
.should('contain', 'Tx hash: test-tx-hash');
cy.getByTestId('dialog-close').click();
};
it.skip('cannot place an order if market is suspended');
it.skip('cannot place an order if size is 0');
it.skip('cannot place an order expiry date is invalid');
it.skip('unsuccessfull order due to no collateral');
});
describe('deal ticket validation', () => {
before(() => {
mockTradingPage(MarketState.Active);
cy.visit('/markets/market-0');
});
it('cannot place an order if wallet is not connected', () => {
cy.getByTestId('connect-vega-wallet'); // Not connected
cy.getByTestId('place-order').should('be.disabled');
cy.getByTestId('dealticket-error-message').contains(
'No public key selected'
);
});
});

View File

@ -0,0 +1,69 @@
import { MarketState } from '@vegaprotocol/types';
import { mockTradingPage } from '../support/trading';
import { connectVegaWallet } from '../support/vega-wallet';
beforeEach(() => {
mockTradingPage(MarketState.Active);
cy.visit('/markets/market-0');
});
describe('orders', () => {
const orderSymbol = 'market.tradableInstrument.instrument.code';
const orderSize = 'size';
const orderType = 'type';
const orderStatus = 'status';
const orderRemaining = 'remaining';
const orderPrice = 'price';
const orderTimeInForce = 'timeInForce';
const orderCreatedAt = 'createdAt';
it('renders orders', () => {
cy.getByTestId('Orders').click();
cy.getByTestId('tab-orders').contains('Please connect Vega wallet');
connectVegaWallet();
cy.getByTestId('tab-orders').should('be.visible');
cy.getByTestId('tab-orders')
.get(`[col-id='${orderSymbol}']`)
.each(($symbol) => {
cy.wrap($symbol).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-orders')
.get(`[col-id='${orderSize}']`)
.each(($size) => {
cy.wrap($size).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-orders')
.get(`[col-id='${orderType}']`)
.each(($type) => {
cy.wrap($type).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-orders')
.get(`[col-id='${orderStatus}']`)
.each(($status) => {
cy.wrap($status).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-orders')
.get(`[col-id='${orderRemaining}']`)
.each(($remaining) => {
cy.wrap($remaining).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-orders')
.get(`[col-id='${orderPrice}']`)
.each(($price) => {
cy.wrap($price).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-orders')
.get(`[col-id='${orderTimeInForce}']`)
.each(($timeInForce) => {
cy.wrap($timeInForce).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-orders')
.get(`[col-id='${orderCreatedAt}']`)
.each(($dateTime) => {
cy.wrap($dateTime).invoke('text').should('not.be.empty');
});
});
});

View File

@ -1,127 +0,0 @@
Feature: Trading page
Scenario Outline: Deal ticket: Successfull market buy orders
Given I am on the trading page for an active market
And I connect to Vega Wallet
When I place a buy '<marketOrderType>' market order
Then order request is sent
Examples:
| marketOrderType |
| FOK |
| IOC |
Scenario Outline: Deal ticket: Successfull Limit buy orders
Given I am on the trading page for an active market
And I connect to Vega Wallet
When I place a buy '<limitOrderType>' limit order
Then order request is sent
Examples:
| limitOrderType |
| IOC |
| FOK |
| GTT |
# | GFA | Requires market to be in auction
| GFN |
Scenario Outline: Deal ticket: Successfull market sell order
Given I am on the trading page for an active market
And I connect to Vega Wallet
When I place a sell '<marketOrderType>' market order
Then order request is sent
Examples:
| marketOrderType |
| FOK |
| IOC |
Scenario Outline: Deal ticket: Successfull limit sell order
Given I am on the trading page for an active market
And I connect to Vega Wallet
When I place a sell '<limitOrderType>' limit order
Then order request is sent
Examples:
| limitOrderType |
| IOC |
| FOK |
| GTT |
# | GFA | Requires market to be in auction
| GFN |
@ignore
Scenario: Deal ticket: Unsuccessfull order because lack of funds
Given I am on the homepage
And I navigate to markets page
When I click on active market
And I connect to Vega Wallet
And place a buy 'FOK' market order
Then error message for insufficient funds is displayed
Scenario: Deal ticket: Unable to order because market is suspended
Given I am on the trading page for a suspended market
And I connect to Vega Wallet
Then place order button is disabled
And "Market is currently suspended" error is shown
Scenario: Deal ticket: Unable to order because wallet is not connected
Given I am on the trading page for an active market
Then place order button is disabled
And "No public key selected" error is shown
@ignore
Scenario: Deal ticket: Unsuccessfull because quantity is 0
Given I am on the trading page for an active market
And I connect to Vega Wallet
And place a buy 'FOK' market order with amount of 0
Then Order rejected by wallet error shown containing text "must be positive"
Scenario: Positions: Displayed when connected to wallet
Given I am on the trading page for an active market
And I connect to Vega Wallet
When I click on positions tab
Then positions are displayed
Scenario: Accounts: Displayed when connected to wallet
Given I am on the trading page for an active market
And I connect to Vega Wallet
When I click on accounts tab
Then accounts are displayed
And I can see account for tEURO
Scenario: Orders: Placed orders displayed
Given I am on the trading page for an active market
And I connect to Vega Wallet
When I click on orders tab
Then placed orders are displayed
Scenario: Orderbook displayed
Given I am on the trading page for an active market
When I click on order book tab
Then orderbook is displayed with expected orders
And orderbook can be reduced and expanded
@todo
Scenario: Orderbook paginated with over 100 orders
Given I am on the trading page for an active market
When I click on order book tab
And a large amount is orders are received
Then a certain amount of orders are displayed
@todo
Scenario: Orderbook uses non-static prices for market in auction
Given I am on the trading page for a market in auction
When I click on order book tab
Then order book is rendered using non-static offers
@todo
Scenario: Orderbook updated when large order is made
Given I am on the trading page for an active market
When I place a large order
Then I should see my order have an effect on the order book
@todo
Scenario: Able to place order by clicking on order from orderbook
Given I am on the trading page for an active market
When I place a large order
Then I should see my order have an effect on the order book

View File

@ -0,0 +1,35 @@
import { MarketState } from '@vegaprotocol/types';
import { mockTradingPage } from '../support/trading';
import { connectVegaWallet } from '../support/vega-wallet';
beforeEach(() => {
mockTradingPage(MarketState.Active);
cy.visit('/markets/market-0');
});
describe('positions', () => {
it('renders positions', () => {
cy.getByTestId('Positions').click();
cy.getByTestId('tab-positions').contains('Please connect Vega wallet');
connectVegaWallet();
cy.getByTestId('tab-positions').should('be.visible');
cy.getByTestId('tab-positions')
.get('[col-id="market.tradableInstrument.instrument.code"]')
.each(($marketSymbol) => {
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
});
cy.getByTestId('tab-positions')
.get('[col-id="openVolume"]')
.each(($openVolume) => {
cy.wrap($openVolume).invoke('text').should('not.be.empty');
});
// includes average entry price, mark price & realised PNL
cy.getByTestId('tab-positions')
.getByTestId('flash-cell')
.each(($prices) => {
cy.wrap($prices).invoke('text').should('not.be.empty');
});
});
});

View File

@ -0,0 +1,34 @@
import { MarketState } from '@vegaprotocol/types';
import { mockTradingPage } from '../support/trading';
beforeEach(() => {
mockTradingPage(MarketState.Active);
cy.visit('/markets/market-0');
});
describe('trades', () => {
const colIdPrice = 'price';
const colIdSize = 'size';
const colIdCreatedAt = 'createdAt';
it('renders trades', () => {
cy.getByTestId('Trades').click();
cy.getByTestId('tab-trades').should('be.visible');
cy.get(`[col-id=${colIdPrice}]`).each(($tradePrice) => {
cy.wrap($tradePrice).invoke('text').should('not.be.empty');
});
cy.get(`[col-id=${colIdSize}]`).each(($tradeSize) => {
cy.wrap($tradeSize).invoke('text').should('not.be.empty');
});
const dateTimeRegex =
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
cy.get(`[col-id=${colIdCreatedAt}]`).each(($tradeDateTime, index) => {
if (index != 0) {
//ignore header
cy.wrap($tradeDateTime).invoke('text').should('match', dateTimeRegex);
}
});
});
});

View File

@ -0,0 +1,97 @@
import { hasOperationName } from '../support';
import { connectEthereumWallet } from '../support/ethereum-wallet';
import { generateWithdrawPageQuery } from '../support/mocks/generate-withdraw-page-query';
import { connectVegaWallet } from '../support/vega-wallet';
describe('withdraw', () => {
const formFieldError = 'input-error-text';
const toAddressField = 'input[name="to"]';
const assetSelectField = 'select[name="asset"]';
const amountField = 'input[name="amount"]';
const useMaximumAmount = 'use-maximum';
const submitWithdrawBtn = 'submit-withdrawal';
beforeEach(() => {
cy.mockWeb3Provider();
cy.mockGQL('WithdrawPageQuery', (req) => {
if (hasOperationName(req, 'WithdrawPageQuery')) {
req.reply({
body: { data: generateWithdrawPageQuery() },
});
}
});
cy.visit('/portfolio/withdraw');
// Withdraw page requires vega wallet connection
connectVegaWallet();
// It also requires connection Ethereum wallet
connectEthereumWallet();
cy.wait('@WithdrawPageQuery');
cy.contains('Withdraw');
});
it('form validation', () => {
// Prompts that there are incomplete withdrawals
cy.contains('You have incomplete withdrawals');
cy.getByTestId('complete-withdrawals-prompt').should('exist');
cy.getByTestId(submitWithdrawBtn).click();
cy.getByTestId(formFieldError).should('contain.text', 'Required');
// only 2 despite 3 fields because the ethereum address will be auto populated
cy.getByTestId(formFieldError).should('have.length', 2);
// Test for invalid Ethereum address
cy.get(toAddressField)
.clear()
.type('invalid-ethereum-address')
.next('[data-testid="input-error-text"]')
.should('contain.text', 'Invalid Ethereum address');
// Test min amount
cy.get(assetSelectField).select('Asset 1'); // Select asset so we have a min viable amount calculated
cy.get(amountField)
.clear()
.type('0')
.next('[data-testid="input-error-text"]')
.should('contain.text', 'Value is below minimum');
// Test max amount
cy.get(amountField)
.clear()
.type('1') // Will be above maximum because the vega wallet doesnt have any collateral
.next('[data-testid="input-error-text"]')
.should('contain.text', 'Value is above maximum');
});
it('can set amount using use maximum button', () => {
cy.get(assetSelectField).select('Asset 0');
cy.getByTestId(useMaximumAmount).click();
cy.get(amountField).should('have.value', '1000.00000');
});
it('triggers transaction when submitted', () => {
cy.mockVegaCommandSync({
txHash: 'test-tx-hash',
tx: {
signature: {
value:
'd86138bba739bbc1069b3dc975d20b3a1517c2b9bdd401c70eeb1a0ecbc502ec268cf3129824841178b8b506b0b7d650c76644dbd96f524a6cb2158fb7121800',
},
},
});
cy.get(assetSelectField).select('Asset 0');
cy.get(amountField).clear().type('10');
cy.getByTestId(submitWithdrawBtn).click();
cy.getByTestId('dialog-title').should(
'have.text',
'Withdrawal transaction pending'
);
cy.getByTestId('dialog-text').should('have.text', 'Awaiting transaction');
});
it.skip('creates a withdrawal on submit'); // Needs capsule
it.skip('creates a withdrawal on submit and prompts to complete withdrawal'); // Needs capsule
});

View File

@ -1,68 +0,0 @@
Feature: Withdrawals to eth wallet
Background:
Given I can connect to Ethereum
And I navigate to withdrawal page
And I connect to Vega Wallet
Scenario: Succesfull withdrawal
When I succesfully fill in and submit withdrawal form
Then withdrawal modal is displayed
Scenario: Error displayed when fields are empty
When I clear ethereum address
And click submit
Then errors are displayed for empty fields
Scenario: Error displayed when invalid Ethereum address is entered
When I enter an invalid ethereum address
Then error for invalid ethereum address is displayed
Scenario: Error displayed when not in range of acceptable amount
When I enter the following details in withdrawal form
| asset | tUSDC TEST |
| amount | 0 |
Then error for below minumum amount is displayed
When I enter the following details in withdrawal form
| asset | tUSDC TEST |
| amount | 1 |
Then error for above maximum amount is displayed
Scenario: Fill in amount using maximum
When I select "tDAI TEST"
And ethereum address is connected Ethereum wallet
And I click Use maximum
Then expected amount is "5.00000"
@ignore
Scenario: Able to view history of withdrawals on withdrawals page
Given I navigate to withdrawals page
Then history of withdrawals are displayed
Scenario: Vega wallet connect text shown when Vega wallet is disconnected
When I disconnect my Vega wallet
Then connect to Vega wallet is displayed
@manual
Scenario: Can see pending / unfinished withdrawals
Given I am on the withdrawals page
And I can see there are unfinished withdrawals
And I can see the complete withdrawals button
# Needs capsule
@manual
Scenario: Finish withdrawal to eth wallet
Given I am on the withdrawals page
And I can see there are unfinished withdrawals
And I click on an unfinished withdrawal button
Then I approve transaction on ethereum
Then I can see the withdrawal button state has changed to pending
When The transaction is complete
Then My balance has been updated
@manual
Scenario: Withdrawals after chain reset
Given I am on the withdrawals page
And I previously had withdrawals
And There has been a chain reset
Then There should be no incomplete withdrawals

View File

@ -0,0 +1,82 @@
import { hasOperationName } from '../support';
import { connectEthereumWallet } from '../support/ethereum-wallet';
import { generateWithdrawals } from '../support/mocks/generate-withdrawals';
import { connectVegaWallet } from '../support/vega-wallet';
describe('withdrawals', () => {
beforeEach(() => {
cy.mockWeb3Provider();
cy.mockGQL('Withdrawals', (req) => {
if (hasOperationName(req, 'Withdrawals')) {
req.reply({
body: { data: generateWithdrawals() },
});
}
});
cy.visit('/portfolio/withdrawals');
// Withdraw page requires vega wallet connection
connectVegaWallet();
// It also requires connection Ethereum wallet
connectEthereumWallet();
cy.contains('Withdrawals');
});
it('renders history of withdrawals', () => {
const ethAddressLink = `${Cypress.env(
'ETHERSCAN_URL'
)}/address/0x72c22822A19D20DE7e426fB84aa047399Ddd8853`;
const etherScanLink = `${Cypress.env(
'ETHERSCAN_URL'
)}/tx/0x5d7b1a35ba6bd23be17bb7a159c13cdbb3121fceb94e9c6c510f5503dce48d03`;
cy.contains('Withdrawals');
const row = '.ag-center-cols-container[role="rowgroup"] > [role="row"]';
// First row is incomplete
cy.get(row)
.eq(0)
.find('[col-id="asset.symbol"]')
.should('contain.text', 'AST0');
cy.get(row)
.eq(0)
.find('[col-id="amount"]')
.should('contain.text', '100.00000');
cy.get(row)
.eq(0)
.find('[col-id="details.receiverAddress"]')
.should('contain.text', '0x72c2…dd8853')
.find('a')
.should('have.attr', 'href', ethAddressLink);
cy.get(row)
.eq(0)
.find('[col-id="createdTimestamp"]')
.invoke('text')
.should('not.be.empty');
cy.get(row)
.eq(0)
.find('[col-id="status"]')
.should('contain.text', 'Open')
.find('button')
.contains('Complete');
// Second row is complete so last cell should have a link to the tx
cy.get(row)
.eq(1)
.find('[col-id="status"]')
.should('contain.text', 'Finalized')
.find('a')
.contains('View on Etherscan')
.should('have.attr', 'href', etherScanLink);
});
it('renders a link to start a new withdrawal', () => {
cy.getByTestId('start-withdrawal').click();
cy.url().should('include', '/portfolio/withdraw');
});
it.skip('renders pending and unfinished withdrawals');
it.skip('can complete unfinished withdrawals'); // Needs capsule
});

View File

@ -0,0 +1,5 @@
export const connectEthereumWallet = () => {
cy.getByTestId('connect-eth-wallet-btn').should('be.enabled').click();
cy.getByTestId('web3-connector-list').should('be.visible');
cy.getByTestId('web3-connector-MetaMask').click();
};

View File

@ -1,22 +0,0 @@
export class EthereumWallet {
connectWalletBtnId = 'connect-eth-wallet-btn';
connectWalletMsgId = 'connect-eth-wallet-msg';
connect() {
cy.getByTestId(this.connectWalletBtnId).should('be.enabled').click();
cy.getByTestId('web3-connector-list').should('be.visible');
cy.getByTestId('web3-connector-MetaMask').click();
}
verifyEthConnectBtnIsDisplayed() {
cy.getByTestId(this.connectWalletBtnId)
.should('be.visible')
.and('have.text', 'Connect');
}
verifyConnectWalletMsg(ethNotConnectedText: string) {
cy.getByTestId(this.connectWalletMsgId)
.should('be.visible')
.and('have.text', ethNotConnectedText);
}
}

View File

@ -1 +0,0 @@
export * from './ethereum-wallet';

View File

@ -37,7 +37,7 @@ export const generateCandles = (override?: PartialDeep<Candles>): Candles => {
];
const defaultResult = {
market: {
id: 'market-id',
id: 'market-0',
decimalPlaces: 5,
tradableInstrument: {
instrument: {

View File

@ -8,10 +8,10 @@ export const generateDealTicketQuery = (
): DealTicketQuery => {
const defaultResult: DealTicketQuery = {
market: {
id: 'market-id',
id: 'market-0',
name: 'ETHBTC Quarterly (30 Jun 2022)',
decimalPlaces: 2,
positionDecimalPlaces: 1,
positionDecimalPlaces: 0,
state: MarketState.Active,
tradingMode: MarketTradingMode.Continuous,
tradableInstrument: {

View File

@ -0,0 +1,36 @@
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
export const generateDepositPage = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
override?: PartialDeep<any>
) => {
const defaultResult = {
assets: [
{
id: 'asset-0',
symbol: 'AST0',
name: 'Asset 0',
decimals: 5,
source: {
__typename: 'ERC20',
contractAddress: '0x5E4b9aDA947130Fc320a144cd22bC1641e5c9d81',
},
__typename: 'Asset',
},
{
id: 'asset-1',
symbol: 'AST1',
name: 'Asset 1',
decimals: 5,
source: {
__typename: 'ERC20',
contractAddress: '0x444b9aDA947130Fc320a144cd22bC1641e5c9d81',
},
__typename: 'Asset',
},
],
};
return merge(defaultResult, override);
};

View File

@ -7,7 +7,7 @@ export const generateMarketList = (
): MarketList => {
const markets: MarketList_markets[] = [
{
id: 'market-id',
id: 'market-0',
decimalPlaces: 5,
data: {
market: {
@ -38,7 +38,7 @@ export const generateMarketList = (
__typename: 'Market',
},
{
id: 'test-market-suspended',
id: 'market-1',
decimalPlaces: 2,
data: {
market: {

View File

@ -14,7 +14,7 @@ export interface Market {
export const generateMarket = (override?: PartialDeep<Market>): Market => {
const defaultResult = {
market: {
id: 'market-id',
id: 'market-0',
name: 'MARKET NAME',
__typename: 'Market',
},

View File

@ -0,0 +1,51 @@
import merge from 'lodash/merge';
import { MarketTradingMode } from '@vegaprotocol/types';
import type { DeepPartial } from 'react-hook-form';
export interface MarketsLanding_markets_marketTimestamps {
__typename: 'MarketTimestamps';
open: string | null;
}
export interface MarketsLanding_markets {
__typename: 'Market';
id: string;
tradingMode: MarketTradingMode;
marketTimestamps: MarketsLanding_markets_marketTimestamps;
}
export interface MarketsLanding {
markets: MarketsLanding_markets[] | null;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const generateMarketsLanding = (
override?: DeepPartial<MarketsLanding>
): MarketsLanding => {
const markets: MarketsLanding_markets[] = [
{
id: 'market-0',
tradingMode: MarketTradingMode.Continuous,
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '1',
},
__typename: 'Market',
},
{
id: 'market-1',
tradingMode: MarketTradingMode.OpeningAuction,
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2',
},
__typename: 'Market',
},
];
const defaultResult: MarketsLanding = {
markets,
};
return merge(defaultResult, override);
};

View File

@ -6,7 +6,7 @@ import type { Markets, Markets_markets } from '@vegaprotocol/market-list';
export const generateMarkets = (override?: PartialDeep<Markets>): Markets => {
const markets: Markets_markets[] = [
{
id: 'market-id',
id: 'market-0',
name: 'ACTIVE MARKET',
decimalPlaces: 5,
data: {
@ -38,7 +38,7 @@ export const generateMarkets = (override?: PartialDeep<Markets>): Markets => {
__typename: 'Market',
},
{
id: 'test-market-suspended',
id: 'market-1',
name: 'SUSPENDED MARKET',
decimalPlaces: 2,
data: {

View File

@ -43,7 +43,7 @@ export const generateTrades = (override?: PartialDeep<Trades>): Trades => {
];
const defaultResult = {
market: {
id: 'market-id',
id: 'market-0',
trades,
__typename: 'Market',
},

View File

@ -0,0 +1,60 @@
import { AccountType } from '@vegaprotocol/types';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
export const generateWithdrawPageQuery = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
override?: PartialDeep<any>
) => {
const defaultResult = {
party: {
id: 'party-0',
withdrawals: [
{
id: 'withdrawal-0',
txHash: null,
__typename: 'Withdrawal',
},
],
accounts: [
{
type: AccountType.General,
balance: '100000000',
asset: {
__typename: 'Asset',
id: 'asset-0',
symbol: 'AST0',
},
__typename: 'Account',
},
],
__typename: 'Party',
},
assets: [
{
id: 'asset-0',
symbol: 'AST0',
name: 'Asset 0',
decimals: 5,
source: {
__typename: 'ERC20',
contractAddress: '0x5E4b9aDA947130Fc320a144cd22bC1641e5c9d81',
},
__typename: 'Asset',
},
{
id: 'asset-1',
symbol: 'AST1',
name: 'Asset 1',
decimals: 5,
source: {
__typename: 'ERC20',
contractAddress: '0x444b9aDA947130Fc320a144cd22bC1641e5c9d81',
},
__typename: 'Asset',
},
],
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1,58 @@
import { WithdrawalStatus } from '@vegaprotocol/types';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
export const generateWithdrawals = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
override?: PartialDeep<any>
) => {
const defaultResult = {
party: {
id: 'party-0',
withdrawals: [
{
id: 'withdrawal-0',
status: WithdrawalStatus.Finalized,
amount: '100',
txHash: null,
createdTimestamp: new Date().toISOString(),
withdrawnTimestamp: new Date().toISOString(),
details: {
__typename: 'Erc20WithdrawalDetails',
receiverAddress: '0x72c22822A19D20DE7e426fB84aa047399Ddd8853',
},
asset: {
__typename: 'Asset',
id: 'asset-0',
symbol: 'AST0',
decimals: 5,
},
__typename: 'Withdrawal',
},
{
id: 'withdrawal-1',
status: WithdrawalStatus.Finalized,
amount: '100',
txHash:
'0x5d7b1a35ba6bd23be17bb7a159c13cdbb3121fceb94e9c6c510f5503dce48d03',
createdTimestamp: new Date().toISOString(),
withdrawnTimestamp: new Date().toISOString(),
details: {
__typename: 'Erc20WithdrawalDetails',
receiverAddress: '0x72c22822A19D20DE7e426fB84aa047399Ddd8853',
},
asset: {
__typename: 'Asset',
id: 'asset-0',
symbol: 'AST0',
decimals: 5,
},
__typename: 'Withdrawal',
},
],
__typename: 'Party',
},
};
return merge(defaultResult, override);
};

View File

@ -1,57 +0,0 @@
export default class BasePage {
closeDialogBtn = 'dialog-close';
portfolioUrl = '/portfolio';
marketsUrl = '/markets';
assetSelectField = 'select[name="asset"]';
toAddressField = 'input[name="to"]';
amountField = 'input[name="amount"]';
formFieldError = 'input-error-text';
dialogHeader = 'dialog-title';
dialogText = 'dialog-text';
closeDialog() {
cy.getByTestId(this.closeDialogBtn, { timeout: 12000 })?.click({
force: true,
});
}
navigateToPortfolio() {
cy.get(`a[href='${this.portfolioUrl}']`)
.first()
.should('be.visible')
.click({ force: true });
cy.url().should('include', '/portfolio');
}
navigateToMarkets() {
cy.get(`a[href='${this.marketsUrl}']`)
.first()
.should('be.visible')
.click({ force: true });
cy.url().should('include', '/markets');
}
verifyFormErrorDisplayed(expectedError: string, expectedNumErrors: number) {
cy.getByTestId(this.formFieldError).should('contain.text', expectedError);
cy.getByTestId(this.formFieldError).should(
'have.length',
expectedNumErrors
);
}
updateTransactionForm(args?: {
asset?: string;
to?: string;
amount?: string;
}) {
if (args?.asset) {
cy.get(this.assetSelectField).select(args.asset);
}
if (args?.to) {
cy.get(this.toAddressField).clear().type(args.to);
}
if (args?.amount) {
cy.get(this.amountField).clear().type(args.amount);
}
}
}

View File

@ -1,55 +0,0 @@
import BasePage from './base-page';
export default class DepositsPage extends BasePage {
requiredText = 'Required';
assetError = '[role="alert"][aria-describedby="asset"]';
toError = '[role="alert"][aria-describedby="to"]';
amountError = '[role="alert"][aria-describedby="amount"]';
depositSubmitBtn = 'deposit-submit';
depositApproveSubmitBtn = 'deposit-approve-submit';
navigateToDeposits() {
cy.visit('/portfolio/deposit');
cy.url().should('include', '/portfolio/deposit');
cy.getByTestId('deposit-form').should('be.visible');
}
verifyFormDisplayed() {
cy.getByTestId('deposit-form').should('be.visible');
}
checkModalContains(text: string) {
cy.get('[role="dialog"] > div > div > h1').should('have.text', text);
}
clickDepositSubmit() {
cy.getByTestId(this.depositSubmitBtn).click();
}
clickDepositApproveSubmit() {
cy.getByTestId(this.depositApproveSubmitBtn).click();
}
verifyInvalidPublicKey() {
cy.get(this.toError).should('have.text', 'Invalid Vega key');
}
verifyAmountTooSmall() {
cy.get(this.amountError).should('have.text', 'Value is below minimum');
}
verifyInsufficientAmountMessage() {
cy.getByTestId('input-error-text').should(
'contain.text',
'Insufficient amount in Ethereum wallet'
);
}
verifyNotApproved() {
cy.get(this.amountError).should(
'have.text',
'Amount is above approved amount'
);
cy.contains('Deposits of tBTC not approved').should('be.visible');
}
}

View File

@ -1,80 +0,0 @@
import BasePage from './base-page';
import type { OpenMarketType } from '../step_definitions/home-page.step';
export default class HomePage extends BasePage {
validateStringIsDisplayedAtTopOfTable(value: string) {
// Ignore header row
cy.get('table tr')
.eq(1)
.within(() => cy.contains(value).should('be.visible'));
}
getOpenMarketsFromServer() {
const query = `{markets{marketTimestamps{open},tradableInstrument{instrument{code,name}}}}`;
return cy
.request({
method: 'POST',
url: `https://lb.testnet.vega.xyz/query`,
body: { query },
headers: { 'content-type': 'application/json' },
})
.its('body.data.markets');
}
getOpenMarketCodes(openMarkets: OpenMarketType[]) {
const openMarketCodes: string[] = [];
openMarkets.forEach((market: OpenMarketType) => {
openMarketCodes.push(market.tradableInstrument.instrument.code);
});
return openMarketCodes;
}
getOldestOpenMarket(openMarkets: OpenMarketType[]) {
const [oldestMarket] = openMarkets.sort(
(a, b) =>
new Date(a.marketTimestamps.open).getTime() -
new Date(b.marketTimestamps.open).getTime()
);
if (!oldestMarket) {
throw new Error('Could not find oldest market');
}
return oldestMarket;
}
getMostRecentOpenMarket(openMarkets: OpenMarketType[]) {
const [recentMarket] = openMarkets.sort(
(b, a) =>
new Date(a.marketTimestamps.open).getTime() -
new Date(b.marketTimestamps.open).getTime()
);
if (!recentMarket) {
throw new Error('Could not find most recent market');
}
return recentMarket;
}
validateTableCodesExistOnServer(openMarketCodes: string[]) {
cy.get('table tr', { timeout: 12000 }).each(($element, index) => {
if (index > 0) {
// skip header row
const openMarketCodeText: string = $element.children().first().text();
assert.include(
openMarketCodes,
openMarketCodeText,
`Checking ${openMarketCodeText} is shown within server open markets response`
);
}
});
}
validateTableContainsLastPriceAndChange() {
cy.get('table tr').each(($element, index) => {
if (index > 0) {
// skip header row
cy.root().within(() => {
cy.getByTestId('price').should('not.be.empty');
});
}
});
}
}

View File

@ -1,60 +0,0 @@
import BasePage from './base-page';
export default class MarketPage extends BasePage {
marketRowHeaderClassname = 'div > span.ag-header-cell-text';
marketRowNameColumn = 'tradableInstrument.instrument.code';
marketRowSymbolColumn =
'tradableInstrument.instrument.product.settlementAsset.symbol';
marketRowPrices = 'flash-cell';
marketRowDescription = 'name';
marketStateColId = 'data';
validateMarketsAreDisplayed() {
// We need this to ensure that ag-grid is fully rendered before asserting
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
cy.get('.ag-root-wrapper').should('be.visible');
}
validateMarketTableDisplayed() {
const expectedMarketHeaders = [
'Market',
'Settlement asset',
'State',
'Best bid',
'Best offer',
'Mark price',
'Description',
];
for (let index = 0; index < expectedMarketHeaders.length; index++) {
cy.get(this.marketRowHeaderClassname).should(
'contain.text',
expectedMarketHeaders[index]
);
}
cy.get(`[col-id='${this.marketRowNameColumn}']`).each(($marketName) => {
cy.wrap($marketName).should('not.be.empty');
});
cy.get(`[col-id='${this.marketRowSymbolColumn}']`).each(($marketSymbol) => {
cy.wrap($marketSymbol).should('not.be.empty');
});
cy.getByTestId(this.marketRowPrices).each(($price) => {
cy.wrap($price).should('not.be.empty').and('contain.text', '.');
});
cy.get(`[col-id='${this.marketRowDescription}']`).each(
($marketDescription) => {
cy.wrap($marketDescription).should('not.be.empty');
}
);
}
clickOnMarket(text: string) {
cy.get(`[col-id=${this.marketStateColId}]`).should('be.visible');
cy.get(`[col-id=${this.marketStateColId}]`).contains(text).click();
}
}

View File

@ -1,44 +0,0 @@
import BasePage from './base-page';
export default class PortfolioPage extends BasePage {
deposit = 'deposit';
depositTEuro = 'deposit-tEuro';
viewWithdrawals = 'view-withdrawals';
withdraw = 'withdraw';
withdrawTEuro = 'withdraw-tEuro';
navigateToDeposit() {
cy.getByTestId(this.deposit)
.should('have.attr', 'href')
.and('include', '/portfolio/deposit');
cy.getByTestId(this.deposit).click();
}
navigateToDepositTEuro() {
cy.getByTestId(this.depositTEuro)
.should('have.attr', 'href')
.and('include', '/portfolio/deposit?assetId');
cy.getByTestId(this.depositTEuro).click();
}
navigateToWithdrawals() {
cy.getByTestId(this.viewWithdrawals)
.should('have.attr', 'href')
.and('include', '/portfolio/withdrawals');
cy.getByTestId(this.viewWithdrawals).click();
}
navigateToWithdraw() {
cy.getByTestId(this.withdraw)
.should('have.attr', 'href')
.and('include', '/portfolio/withdraw');
cy.getByTestId(this.withdraw).click();
}
navigateToWithdrawTEuro() {
cy.getByTestId(this.withdrawTEuro)
.should('have.attr', 'href')
.and('include', '/portfolio/withdraw?assetId');
cy.getByTestId(this.withdrawTEuro).click();
}
}

View File

@ -1,41 +0,0 @@
import BasePage from './base-page';
export default class TradingPage extends BasePage {
chartTab = 'Chart';
ticketTab = 'Ticket';
orderbookTab = 'Orderbook';
ordersTab = 'Orders';
positionsTab = 'Positions';
accountsTab = 'Accounts';
collateralTab = 'Collateral';
tradesTab = 'Trades';
completedTrades = 'Market-trades';
clickOnOrdersTab() {
cy.getByTestId(this.ordersTab).click();
}
clickOnAccountsTab() {
cy.getByTestId(this.accountsTab).click();
}
clickOnPositionsTab() {
cy.getByTestId(this.positionsTab).click();
}
clickOnTicketTab() {
cy.getByTestId(this.ticketTab).click();
}
clickOnCollateralTab() {
cy.getByTestId(this.collateralTab).click();
}
clickOnTradesTab() {
cy.getByTestId(this.tradesTab).click();
}
clickOrderBookTab() {
cy.getByTestId(this.orderbookTab).click();
}
}

View File

@ -1,94 +0,0 @@
import BasePage from './base-page';
export default class WithdrawalsPage extends BasePage {
useConnectedEthWallet = 'use-connected';
useMaximumAmount = 'use-maximum';
submitBtn = 'submit-withdrawal';
connectVegaWalletText = 'connect-vega-wallet-text';
assetSymbolColId = 'asset.symbol';
amountColId = 'amount';
recipientColdId = 'details.receiverAddress';
createdAtTimeStampId = 'createdTimestamp';
statusColId = 'status';
etherScanLink = 'etherscan-link';
clearEthereumAddress() {
cy.get(this.toAddressField).clear();
}
clickUseConnected() {
cy.getByTestId(this.useConnectedEthWallet).click();
}
clickUseMaximum() {
cy.getByTestId(this.useMaximumAmount).click();
}
clickSubmit() {
cy.getByTestId(this.submitBtn).click();
}
validateConnectWalletText() {
cy.getByTestId(this.connectVegaWalletText).should(
'have.text',
'Connect your Vega wallet'
);
}
validateTestWalletEthWalletAddress() {
cy.get(this.toAddressField).should(
'have.value',
Cypress.env('ETHEREUM_WALLET_ADDRESS')
);
}
validateAmount(expectedAmount: string) {
cy.get(this.amountField).should('have.value', expectedAmount);
}
validateConfirmWithdrawalModal() {
cy.getByTestId(this.dialogHeader).should('have.text', 'Confirm withdrawal');
cy.getByTestId(this.dialogText).should(
'have.text',
'Confirm withdrawal in Vega wallet'
);
}
validateWithdrawalAssetDisplayed(assetSymbol: string) {
cy.get(`[col-id="${this.assetSymbolColId}"]`).should(
'contain.text',
assetSymbol
);
}
validateWithdrawalAmountDisplayed(amount: string) {
cy.get(`[col-id="${this.amountColId}"]`).should('contain.text', amount);
}
validateWithdrawalRecipientDisplayed(
truncatedEthAddress: string,
ethAddressLink: string
) {
cy.get(`[col-id="${this.recipientColdId}"]`)
.should('contain.text', truncatedEthAddress)
.find(`[data-testid=${this.etherScanLink}]`)
.should('have.attr', 'href', ethAddressLink);
}
validateWithdrawalDateDisplayed() {
cy.get(`[col-id="${this.createdAtTimeStampId}"]`)
.invoke('text')
.should('not.be.empty');
}
validateWithdrawalStatusDisplayed(status: string) {
cy.get(`[col-id="${this.statusColId}"]`).should('contain.text', status);
}
validateEtherScanLinkDisplayed(txlink: string) {
cy.getByTestId(this.etherScanLink)
.last()
.should('have.text', 'View on Etherscan')
.and('have.attr', 'href', txlink);
}
}

View File

@ -1,29 +0,0 @@
import { Given, When } from 'cypress-cucumber-preprocessor/steps';
import { hasOperationName } from '..';
import { generateMarketList } from '../mocks/generate-market-list';
import BasePage from '../pages/base-page';
const basePage = new BasePage();
Given('I am on the homepage', () => {
cy.mockGQL('MarketsList', (req) => {
if (hasOperationName(req, 'MarketsList')) {
req.reply({
body: { data: generateMarketList() },
});
}
});
cy.visit('/');
cy.getByTestId('market', { timeout: 60000 }).should('be.visible', {
timeout: 20000,
});
cy.contains('Loading...', { timeout: 20000 }).should('not.exist');
});
When('I close the dialog form', () => {
basePage.closeDialog();
});
Given('I can connect to Ethereum', () => {
cy.mockWeb3Provider();
});

View File

@ -1,52 +0,0 @@
import { Then, When } from 'cypress-cucumber-preprocessor/steps';
import DealTicket from '../trading-windows/deal-ticket';
const dealTicket = new DealTicket();
When('I place a buy {string} market order', (orderType) => {
dealTicket.placeMarketOrder(true, '100', orderType);
dealTicket.clickPlaceOrder();
});
When('I place a sell {string} market order', (orderType) => {
dealTicket.placeMarketOrder(false, '100', orderType);
dealTicket.clickPlaceOrder();
});
When('I place a buy {string} limit order', (limitOrderType) => {
dealTicket.placeLimitOrder(true, '100', '2000', limitOrderType);
dealTicket.clickPlaceOrder();
});
When('I place a sell {string} limit order', (limitOrderType) => {
dealTicket.placeLimitOrder(false, '100', '2000', limitOrderType);
dealTicket.clickPlaceOrder();
});
When('I place a buy {string} market order with amount of 0', (orderType) => {
dealTicket.placeMarketOrder(true, '0', orderType);
dealTicket.clickPlaceOrder();
});
Then('order request is sent', () => {
dealTicket.verifyOrderRequestSent();
});
Then('error message for insufficient funds is displayed', () => {
dealTicket.verifyOrderFailedInsufficientFunds();
});
Then('place order button is disabled', () => {
dealTicket.verifyPlaceOrderBtnDisabled();
});
Then('{string} error is shown', (errorMsg) => {
dealTicket.verifySubmitBtnErrorText(errorMsg);
});
Then(
'Order rejected by wallet error shown containing text {string}',
(expectedError) => {
dealTicket.verifyOrderRejected(expectedError);
}
);

View File

@ -1,71 +0,0 @@
import { And, Then, When } from 'cypress-cucumber-preprocessor/steps';
import { EthereumWallet } from '../ethereum-wallet';
import DepositsPage from '../pages/deposits-page';
const depositsPage = new DepositsPage();
const ethWallet = new EthereumWallet();
Then('I navigate to deposits page', () => {
depositsPage.navigateToDeposits();
});
Then('I can see the eth not connected message {string}', (message) => {
ethWallet.verifyConnectWalletMsg(message);
});
And('the connect button is displayed', () => {
ethWallet.verifyEthConnectBtnIsDisplayed();
});
When('I connect my Ethereum wallet', () => {
ethWallet.connect();
});
Then('I can see the deposit form', () => {
depositsPage.verifyFormDisplayed();
});
When('I submit a deposit with empty fields', () => {
depositsPage.updateTransactionForm();
depositsPage.clickDepositSubmit();
});
Then('I can see empty form validation errors present', () => {
depositsPage.verifyFormErrorDisplayed('Required', 3);
});
Then('I enter the following deposit details in deposit form', (table) => {
depositsPage.updateTransactionForm({
asset: table.rowsHash().asset,
to: Cypress.env(table.rowsHash().to),
amount: table.rowsHash().amount,
});
});
And('I submit the form', () => {
depositsPage.clickDepositSubmit();
});
Then('Invalid Vega key is shown', () => {
depositsPage.verifyInvalidPublicKey();
});
Then('Amount too small message shown', () => {
depositsPage.verifyAmountTooSmall();
});
And('I enter a valid amount', () => {
depositsPage.updateTransactionForm({ amount: '1' });
});
Then('Not approved message shown', () => {
depositsPage.verifyNotApproved();
});
And('I can see the {string} modal is shown', (text) => {
depositsPage.checkModalContains(text);
});
And('Insufficient amount message shown', () => {
depositsPage.verifyInsufficientAmountMessage();
});

View File

@ -1,164 +0,0 @@
import { Then, When } from 'cypress-cucumber-preprocessor/steps';
import VegaWallet from '../vega-wallet';
import HomePage from '../pages/home-page';
const vegaWallet = new VegaWallet();
const homePage = new HomePage();
export interface OpenMarketType {
marketTimestamps: {
open: string;
};
tradableInstrument: {
instrument: {
code: string;
name: string;
};
};
}
When('I query the server for Open Markets', function () {
homePage.getOpenMarketsFromServer().then((openMarkets: OpenMarketType[]) => {
cy.wrap(openMarkets).as('openMarketData');
});
});
Then('I am prompted to select a market', () => {
cy.contains('Select a market to get started', { timeout: 20000 }).should(
'be.visible'
);
});
Then('the choose market overlay is no longer showing', () => {
cy.contains('Select a market to get started').should('not.exist');
cy.contains('Loading...', { timeout: 20000 }).should('not.exist');
});
Then(
'the server contains at least {int} open markets',
(expectedNumber: number) => {
cy.get('@openMarketData')
.its('length')
.should('be.at.least', expectedNumber);
}
);
Then(
'I expect the market overlay table to contain at least {int} rows',
(expectedNumber: number) => {
cy.get('table tr').then((row) => {
expect(row.length).to.be.at.least(expectedNumber);
});
}
);
Then(
'each market shown in overlay table exists as open market on server',
() => {
const arrayOfOpenMarketCodes: string[] = [];
cy.get('@openMarketData')
.each((openMarket: OpenMarketType) => {
arrayOfOpenMarketCodes.push(
openMarket.tradableInstrument.instrument.code
);
})
.then(() => {
homePage.validateTableCodesExistOnServer(arrayOfOpenMarketCodes);
});
}
);
Then(
'each market shown in overlay table contains content under the last price and change fields',
() => {
homePage.validateTableContainsLastPriceAndChange();
}
);
Then(
'each market shown in the full list exists as open market on server',
() => {
cy.get('@openMarketData').each((openMarket: OpenMarketType) => {
cy.contains(openMarket.tradableInstrument.instrument.code).should(
'be.visible'
);
});
}
);
Then(
'the oldest market trading in continous mode shown at top of overlay table',
() => {
cy.get<OpenMarketType[]>('@openMarketData').then(
(openMarkets: OpenMarketType[]) => {
const oldestMarket = homePage.getOldestOpenMarket(openMarkets);
homePage.validateStringIsDisplayedAtTopOfTable(
oldestMarket.tradableInstrument.instrument.code
);
}
);
}
);
Then('the oldest current trading market is loaded on the trading tab', () => {
cy.get<OpenMarketType[]>('@openMarketData').then(
(openMarkets: OpenMarketType[]) => {
const oldestMarket = homePage.getOldestOpenMarket(openMarkets);
cy.getByTestId('market', { timeout: 12000 }).within(() => {
cy.get('button')
.contains(oldestMarket.tradableInstrument.instrument.name)
.should('be.visible');
});
}
);
});
Then(
'the most recent current trading market is loaded on the trading tab',
() => {
cy.get<OpenMarketType[]>('@openMarketData').then(
(openMarkets: OpenMarketType[]) => {
const latestMarket = homePage.getMostRecentOpenMarket(openMarkets);
cy.getByTestId('market', { timeout: 12000 }).within(() => {
cy.get('button')
.contains(latestMarket.tradableInstrument.instrument.name)
.should('be.visible');
});
}
);
}
);
When('I click the most recent trading market', () => {
cy.get<OpenMarketType[]>('@openMarketData').then(
(openMarkets: OpenMarketType[]) => {
const latestMarket = homePage.getMostRecentOpenMarket(openMarkets);
cy.contains(latestMarket.tradableInstrument.instrument.code).click();
}
);
});
When('I click the view full market list', () => {
cy.contains('Or view full market list').click();
cy.contains('Loading...').should('be.visible');
cy.contains('Loading...').should('not.exist');
});
When('I try to connect Vega wallet with incorrect details', () => {
vegaWallet.openVegaWalletConnectDialog();
vegaWallet.fillInWalletForm('name', 'wrong passphrase');
vegaWallet.clickConnectVegaWallet();
});
When('I try to connect Vega wallet with blank fields', () => {
vegaWallet.openVegaWalletConnectDialog();
vegaWallet.clickConnectVegaWallet();
});
Then('wallet not running error message is displayed', () => {
vegaWallet.validateWalletNotRunningError();
});
Then('wallet field validation errors are shown', () => {
vegaWallet.validateWalletErrorFieldsDisplayed();
});

View File

@ -1,45 +0,0 @@
import { And, Given, Then, When } from 'cypress-cucumber-preprocessor/steps';
import { hasOperationName } from '..';
import { generateMarkets } from '../mocks/generate-markets';
import MarketsPage from '../pages/markets-page';
const marketsPage = new MarketsPage();
const mockMarkets = () => {
cy.log('Mocking markets query');
cy.mockGQL('Markets', (req) => {
if (hasOperationName(req, 'Markets')) {
req.reply({
body: { data: generateMarkets() },
});
}
});
};
Then('I navigate to markets page', () => {
mockMarkets();
marketsPage.navigateToMarkets();
cy.wait('@Markets');
});
Then('I can view markets', () => {
marketsPage.validateMarketsAreDisplayed();
});
Given('I am on the markets page', () => {
mockMarkets();
cy.visit('/markets');
cy.wait('@Markets');
});
Then('I can view markets', () => {
marketsPage.validateMarketsAreDisplayed();
});
And('the market table is displayed', () => {
marketsPage.validateMarketTableDisplayed();
});
When('I click on {string} market', (Expectedmarket) => {
marketsPage.clickOnMarket(Expectedmarket);
});

View File

@ -1,8 +0,0 @@
import { Then } from 'cypress-cucumber-preprocessor/steps';
import PortfolioPage from '../pages/portfolio-page';
const portfolioPage = new PortfolioPage();
Then('I navigate to portfolio page', () => {
portfolioPage.navigateToPortfolio();
});

View File

@ -1,245 +0,0 @@
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps';
import { hasOperationName } from '..';
import { MarketState } from '@vegaprotocol/types';
import { generateChart } from '../mocks/generate-chart';
import { generateCandles } from '../mocks/generate-candles';
import { generateTrades } from '../mocks/generate-trades';
import { generateDealTicketQuery } from '../mocks/generate-deal-ticket-query';
import { generateMarket } from '../mocks/generate-market';
import { generateOrders } from '../mocks/generate-orders';
import { generatePositions } from '../mocks/generate-positions';
import { generateOrderBook } from '../mocks/generate-order-book';
import { generateAccounts } from '../mocks/generate-accounts';
import PositionsList from '../trading-windows/positions-list';
import AccountsList from '../trading-windows/accounts-list';
import TradesList from '../trading-windows/trades-list';
import TradingPage from '../pages/trading-page';
import OrdersList from '../trading-windows/orders-list';
import OrderBookList from '../trading-windows/orderbook-list';
import MarketPage from '../pages/markets-page';
const tradesList = new TradesList();
const tradingPage = new TradingPage();
const positionsList = new PositionsList();
const accountList = new AccountsList();
const ordersList = new OrdersList();
const orderBookList = new OrderBookList();
const marketPage = new MarketPage();
const mockMarket = (state: MarketState) => {
cy.mockGQL('Market', (req) => {
if (hasOperationName(req, 'Market')) {
req.reply({
body: {
data: generateMarket({
market: {
name: `${state.toUpperCase()} MARKET`,
},
}),
},
});
}
if (hasOperationName(req, 'Orders')) {
req.reply({
body: { data: generateOrders() },
});
}
if (hasOperationName(req, 'Accounts')) {
req.reply({
body: {
data: generateAccounts(),
},
});
}
if (hasOperationName(req, 'Positions')) {
req.reply({
body: { data: generatePositions() },
});
}
if (hasOperationName(req, 'DealTicketQuery')) {
req.reply({
body: { data: generateDealTicketQuery({ market: { state } }) },
});
}
if (hasOperationName(req, 'Trades')) {
req.reply({
body: { data: generateTrades() },
});
}
if (hasOperationName(req, 'Chart')) {
req.reply({
body: { data: generateChart() },
});
}
if (hasOperationName(req, 'MarketDepth')) {
req.reply({
body: { data: generateOrderBook() },
});
}
if (hasOperationName(req, 'Candles')) {
req.reply({
body: { data: generateCandles() },
});
}
});
};
Given('I am on the trading page for an active market', () => {
mockMarket(MarketState.Active);
cy.visit('/markets/market-id');
cy.wait('@Market');
cy.contains('ACTIVE MARKET');
});
Given('I am on the trading page for a suspended market', () => {
mockMarket(MarketState.Suspended);
cy.visit('/markets/market-id');
cy.wait('@Market');
cy.contains('SUSPENDED MARKET');
});
When('I click on {string} mocked market', (marketType) => {
switch (marketType) {
case 'Active':
mockMarket(MarketState.Active);
break;
case 'Suspended':
mockMarket(MarketState.Suspended);
break;
}
marketPage.clickOnMarket(marketType);
});
Then('trading page for {string} market is displayed', (marketType) => {
switch (marketType) {
case 'active':
cy.wait('@Market');
cy.contains('ACTIVE MARKET');
break;
case 'suspended':
cy.wait('@Market');
cy.contains('SUSPENDED MARKET');
break;
}
tradingPage.clickOnTradesTab();
tradesList.verifyTradesListDisplayed();
});
When('I click on orders tab', () => {
tradingPage.clickOnOrdersTab();
});
Then('placed orders are displayed', () => {
ordersList.verifyOrdersDisplayed();
});
When('I click on accounts tab', () => {
tradingPage.clickOnAccountsTab();
});
Then('accounts are displayed', () => {
accountList.verifyAccountsDisplayed();
});
Then('I can see account for tEURO', () => {
accountList.verifySingleAccountDisplayed(
'General-tEURO-null',
'tEURO',
'General',
'—',
'1,000.00000'
);
});
When('I click on positions tab', () => {
tradingPage.clickOnPositionsTab();
});
Then('positions are displayed', () => {
positionsList.verifyPositionsDisplayed();
});
When('I click on order book tab', () => {
tradingPage.clickOrderBookTab();
});
Then('orderbook is displayed with expected orders', () => {
orderBookList.verifyOrderBookRow('826342', '0', '8.26342', '264', '1488');
orderBookList.verifyOrderBookRow('826336', '1475', '8.26336', '0', '1675');
orderBookList.verifyDisplayedVolume(
'826342',
false,
'18%',
orderBookList.testingVolume.AskVolume
);
orderBookList.verifyDisplayedVolume(
'826331',
true,
'100%',
orderBookList.testingVolume.CumulativeVolume
);
// mid level price
orderBookList.verifyOrderBookRow('826337', '0', '8.26337', '0', '200');
orderBookList.verifyDisplayedVolume(
'826337',
true,
'6%',
orderBookList.testingVolume.CumulativeVolume
);
orderBookList.verifyTopMidPricePosition('129');
orderBookList.verifyBottomMidPricePosition('151');
// autofilled order
orderBookList.verifyOrderBookRow('826330', '0', '8.26330', '0', '3548');
orderBookList.verifyDisplayedVolume(
'826330',
true,
'0%',
orderBookList.testingVolume.BidVolume
);
orderBookList.verifyDisplayedVolume(
'826330',
true,
'100%',
orderBookList.testingVolume.CumulativeVolume
);
});
Then('orderbook can be reduced and expanded', () => {
orderBookList.changePrecision('10');
orderBookList.verifyOrderBookRow(
'82634',
'1868',
'8.2634',
'1488',
'1488/1868'
);
orderBookList.verifyCumulativeAskBarPercentage('42%');
orderBookList.verifyCumulativeBidBarPercentage('53%');
orderBookList.changePrecision('100');
orderBookList.verifyOrderBookRow('8263', '3568', '8.263', '1488', '');
orderBookList.verifyDisplayedVolume(
'8263',
true,
'100%',
orderBookList.testingVolume.BidVolume
);
orderBookList.verifyDisplayedVolume(
'8263',
false,
'42%',
orderBookList.testingVolume.AskVolume
);
orderBookList.changePrecision('1');
orderBookList.verifyOrderBookRow('826342', '0', '8.26342', '264', '1488');
});

View File

@ -1,56 +0,0 @@
import { When, Then } from 'cypress-cucumber-preprocessor/steps';
import VegaWallet from '../vega-wallet';
const vegaWallet = new VegaWallet();
beforeEach(() => {
// The two important values here are the signature.value and the From.Pubkey.
// From.PubKey is the first public key in the UI_Trading_Test wallet. We assert that the returned pubkey matches
// what is used to sign the transaction
// The tx.signature.value isn't currently used in any tests but the value returned is used to create IDs objects such
// as orders, if its invalid an erro will be thrown
cy.mockVegaCommandSync({
txHash: 'test-tx-hash',
tx: {
signature: {
value:
'd86138bba739bbc1069b3dc975d20b3a1517c2b9bdd401c70eeb1a0ecbc502ec268cf3129824841178b8b506b0b7d650c76644dbd96f524a6cb2158fb7121800',
},
},
});
});
When('I connect to Vega Wallet', () => {
vegaWallet.openVegaWalletConnectDialog();
vegaWallet.fillInWalletForm(
Cypress.env('TRADING_TEST_VEGA_WALLET_NAME'),
Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE')
);
vegaWallet.clickConnectVegaWallet();
vegaWallet.validateWalletConnected();
});
When('I open wallet dialog', () => {
vegaWallet.validatePublicKeyDisplayed(
Cypress.env('TRUNCATED_VEGA_PUBLIC_KEY')
); // Default Test wallet pub key
vegaWallet.clickOnWalletConnectDialog();
});
When('select a different public key', () => {
vegaWallet.selectPublicKey();
});
When('I disconnect my Vega wallet', () => {
vegaWallet.validatePublicKeyDisplayed(
Cypress.env('TRUNCATED_VEGA_PUBLIC_KEY')
);
vegaWallet.clickOnWalletConnectDialog();
vegaWallet.clickDisconnectAllKeys();
});
Then('public key is switched', () => {
vegaWallet.validatePublicKeyDisplayed(
Cypress.env('TRUNCATED_VEGA_PUBLIC_KEY2')
); // Second public key for test wallet
});

View File

@ -1,117 +0,0 @@
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps';
import PortfolioPage from '../pages/portfolio-page';
import WithdrawalsPage from '../pages/withdrawals-page';
const portfolioPage = new PortfolioPage();
const withdrawalsPage = new WithdrawalsPage();
Given('I navigate to withdrawal page', () => {
cy.visit('/');
portfolioPage.closeDialog();
// portfolioPage.navigateToPortfolio();
// portfolioPage.navigateToWithdraw();
// Navigation functions commented out due to button being removed and not added back in yet
cy.visit('/portfolio/withdraw');
});
Given('I navigate to withdrawals page', () => {
// portfolioPage.navigateToPortfolio();
// portfolioPage.navigateToWithdrawals();
// Navigation functions commented out due to button being removed and not added back in yet
cy.visit('/portfolio/withdrawals');
});
When('I clear ethereum address', () => {
withdrawalsPage.clearEthereumAddress();
});
When('click submit', () => {
withdrawalsPage.clickSubmit();
});
When('I enter an invalid ethereum address', () => {
withdrawalsPage.updateTransactionForm({
to: '0x0dAAACaa868f87BB4666F918742141cAEAe893Fa',
});
withdrawalsPage.clickSubmit();
});
When('I select {string}', (selectedAsset) => {
withdrawalsPage.updateTransactionForm({
asset: selectedAsset,
});
});
When('ethereum address is connected Ethereum wallet', () => {
withdrawalsPage.validateTestWalletEthWalletAddress();
});
When('I click Use maximum', () => {
withdrawalsPage.clickUseMaximum();
});
When('I enter the following details in withdrawal form', (table) => {
withdrawalsPage.updateTransactionForm({
asset: table.rowsHash().asset,
to: table.rowsHash().to,
amount: table.rowsHash().amount,
});
withdrawalsPage.clickSubmit();
});
When('I succesfully fill in and submit withdrawal form', () => {
withdrawalsPage.updateTransactionForm({
asset: Cypress.env('WITHDRAWAL_ASSET_ID'),
amount: '0.1',
});
withdrawalsPage.clickSubmit();
});
Then('errors are displayed for empty fields', () => {
withdrawalsPage.verifyFormErrorDisplayed('Required', 3);
});
Then('error for invalid ethereum address is displayed', () => {
// Expecting empty field errors to still be displayed
withdrawalsPage.verifyFormErrorDisplayed('Invalid Ethereum address', 3);
});
Then('connect to Vega wallet is displayed', () => {
withdrawalsPage.validateConnectWalletText();
});
Then('expected amount is {string}', (expectedAmount) => {
withdrawalsPage.validateAmount(expectedAmount);
});
Then('withdrawal modal is displayed', () => {
withdrawalsPage.validateConfirmWithdrawalModal();
});
Then('error for below minumum amount is displayed', () => {
withdrawalsPage.verifyFormErrorDisplayed('Value is below minimum', 1);
});
Then('error for above maximum amount is displayed', () => {
withdrawalsPage.verifyFormErrorDisplayed('Value is above maximum', 1);
});
Then('history of withdrawals are displayed', () => {
const ethAddressLink = `${Cypress.env('ETHERSCAN_URL')}/address/${Cypress.env(
'ETHEREUM_WALLET_ADDRESS'
)}`;
const etherScanLink = `${Cypress.env(
'ETHERSCAN_URL'
)}/tx/0x0d1a5d209f468ff248326d4ae7647ad5a3667ce463341a0250118a95f3beb597`;
withdrawalsPage.validateWithdrawalAssetDisplayed('tEURO');
withdrawalsPage.validateWithdrawalAmountDisplayed('10,000.00000');
withdrawalsPage.validateWithdrawalRecipientDisplayed(
'0x265C…807158',
ethAddressLink
);
withdrawalsPage.validateWithdrawalDateDisplayed();
withdrawalsPage.validateWithdrawalStatusDisplayed('Finalized');
withdrawalsPage.validateEtherScanLinkDisplayed(etherScanLink);
});

View File

@ -1,44 +0,0 @@
export default class AccountsList {
accountSymbolColId = 'asset.symbol';
accountTypeColId = 'type';
accountMarketNameColId = 'market.name';
accountBalanceColId = 'balance';
verifyAccountsDisplayed() {
cy.get(`[col-id='${this.accountSymbolColId}']`).each(($accountSymbol) => {
cy.wrap($accountSymbol).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${this.accountTypeColId}']`).each(($accountType) => {
cy.wrap($accountType).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${this.accountMarketNameColId}']`).each(
($accountMarketName) => {
cy.wrap($accountMarketName).invoke('text').should('not.be.empty');
}
);
cy.get(`[col-id='${this.accountBalanceColId}']`).each(($accountBalance) => {
cy.wrap($accountBalance).invoke('text').should('not.be.empty');
});
}
verifySingleAccountDisplayed(
accountRowId: string,
accountSymbol: string,
accountType: string,
accountMarketName: string,
accountBalance: string
) {
cy.get(`[row-id='${accountRowId}']`)
.find(`[col-id='${this.accountSymbolColId}']`)
.should('have.text', accountSymbol);
cy.get(`[row-id='${accountRowId}']`)
.find(`[col-id='${this.accountTypeColId}']`)
.should('have.text', accountType);
cy.get(`[row-id='${accountRowId}']`)
.find(`[col-id='${this.accountMarketNameColId}']`)
.should('have.text', accountMarketName);
cy.get(`[row-id='${accountRowId}']`)
.find(`[col-id='${this.accountBalanceColId}']`)
.should('have.text', accountBalance);
}
}

View File

@ -1,122 +0,0 @@
export default class DealTicket {
marketOrderType = 'order-type-TYPE_MARKET';
limitOrderType = 'order-type-TYPE_LIMIT';
buyOrder = 'order-side-SIDE_BUY';
sellOrder = 'order-side-SIDE_SELL';
orderSizeField = 'order-size';
orderPriceField = 'order-price';
orderTypeDropDown = 'order-tif';
datePickerField = 'date-picker-field';
placeOrderBtn = 'place-order';
placeOrderFormError = 'dealticket-error-message';
orderDialog = 'order-wrapper';
orderStatusHeader = 'order-status-header';
orderTransactionHash = 'tx-hash';
orderErrorTxt = 'error-reason';
placeMarketOrder(isBuy: boolean, orderSize: string, orderType: string) {
cy.get(`[data-testid=${this.placeOrderBtn}]`, { timeout: 8000 }).should(
'be.visible'
);
if (isBuy == false) {
cy.getByTestId(this.sellOrder)?.click();
}
cy.getByTestId(this.orderSizeField)?.clear().type(orderSize);
cy.getByTestId(this.orderTypeDropDown)?.select(orderType);
}
placeLimitOrder(
isBuy: boolean,
orderSize: string,
orderPrice: string,
orderType: string
) {
cy.getByTestId(this.limitOrderType)?.click();
if (isBuy == false) {
cy.getByTestId(this.sellOrder)?.click();
}
cy.getByTestId(this.orderSizeField).clear().type(orderSize);
cy.getByTestId(this.orderPriceField).clear().type(orderPrice);
cy.getByTestId(this.orderTypeDropDown).select(orderType);
if (orderType == 'GTT') {
const today = new Date(new Date().setSeconds(0));
const futureDate = new Date(today.setMonth(today.getMonth() + 1)); // set expiry to one month from now
const formattedDate = this.formatDate(futureDate);
cy.getByTestId(this.datePickerField).click().type(formattedDate);
}
}
verifyOrderRequestSent() {
cy.getByTestId(this.orderStatusHeader).should(
'have.text',
'Awaiting network confirmation'
);
cy.getByTestId(this.orderTransactionHash)
.invoke('text')
.should('contain', 'Tx hash: test-tx-hash');
}
verifyOrderFailedInsufficientFunds() {
cy.get(`[data-testid=${this.orderErrorTxt}]`, { timeout: 8000 }).should(
'have.text',
'Reason: InsufficientAssetBalance'
);
}
clickPlaceOrder() {
cy.getByTestId(this.placeOrderBtn).click();
cy.contains('Awaiting network confirmation');
}
verifyPlaceOrderBtnDisabled() {
cy.getByTestId(this.placeOrderBtn).should('be.disabled');
}
verifySubmitBtnErrorText(expectedText: string) {
cy.getByTestId('dealticket-error-message').should(
'have.text',
expectedText
);
}
verifyOrderRejected(errorMsg: string) {
cy.getByTestId(this.orderStatusHeader).should(
'have.text',
'Order rejected by wallet'
);
cy.getByTestId(this.orderDialog)
.find('pre')
.should('contain.text', errorMsg);
}
reloadPageIfPublicKeyErrorDisplayed() {
cy.get('body').then(($body) => {
if ($body.find(`[data-testid=${this.placeOrderFormError}]`).length) {
cy.getByTestId(this.placeOrderFormError)
.invoke('text')
.then(($errorText) => {
if ($errorText == 'No public key selected') {
cy.reload;
}
});
}
});
}
formatDate(date: Date) {
const padZero = (num: number) => num.toString().padStart(2, '0');
const year = date.getFullYear();
const month = padZero(date.getMonth() + 1);
const day = padZero(date.getDate());
const hours = padZero(date.getHours());
const minutes = padZero(date.getMinutes());
return `${year}-${month}-${day}T${hours}:${minutes}`;
}
}

View File

@ -1,37 +0,0 @@
export default class OrdersList {
orderSymbol = 'market.tradableInstrument.instrument.code';
orderSize = 'size';
orderType = 'type';
orderStatus = 'status';
orderRemaining = 'remaining';
orderPrice = 'price';
orderTimeInForce = 'timeInForce';
orderCreatedAt = 'createdAt';
verifyOrdersDisplayed() {
cy.get(`[col-id='${this.orderSymbol}']`).each(($symbol) => {
cy.wrap($symbol).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${this.orderSize}']`).each(($size) => {
cy.wrap($size).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${this.orderType}']`).each(($type) => {
cy.wrap($type).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${this.orderStatus}']`).each(($status) => {
cy.wrap($status).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${this.orderRemaining}']`).each(($remaining) => {
cy.wrap($remaining).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${this.orderPrice}']`).each(($price) => {
cy.wrap($price).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${this.orderTimeInForce}']`).each(($timeInForce) => {
cy.wrap($timeInForce).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${this.orderCreatedAt}']`).each(($dateTime) => {
cy.wrap($dateTime).invoke('text').should('not.be.empty');
});
}
}

View File

@ -1,18 +0,0 @@
export default class PositionsList {
positionMarketSymbol = 'market.tradableInstrument.instrument.code';
positionOpenVolume = 'openVolume';
positionPrices = 'flash-cell';
verifyPositionsDisplayed() {
cy.get(`[col-id='${this.positionMarketSymbol}']`).each(($marketSymbol) => {
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
});
cy.get(`[col-id='${this.positionOpenVolume}']`).each(($openVolume) => {
cy.wrap($openVolume).invoke('text').should('not.be.empty');
});
// includes average entry price, mark price & realised PNL
cy.getByTestId(this.positionPrices).each(($prices) => {
cy.wrap($prices).invoke('text').should('not.be.empty');
});
}
}

View File

@ -1,23 +0,0 @@
export default class TradesList {
colIdPrice = 'price';
colIdSize = 'size';
colIdCreatedAt = 'createdAt';
verifyTradesListDisplayed() {
cy.get(`[col-id=${this.colIdPrice}]`).each(($tradePrice) => {
cy.wrap($tradePrice).invoke('text').should('not.be.empty');
});
cy.get(`[col-id=${this.colIdSize}]`).each(($tradeSize) => {
cy.wrap($tradeSize).invoke('text').should('not.be.empty');
});
const dateTimeRegex =
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
cy.get(`[col-id=${this.colIdCreatedAt}]`).each(($tradeDateTime, index) => {
if (index != 0) {
//ignore header
cy.wrap($tradeDateTime).invoke('text').should('match', dateTimeRegex);
}
});
}
}

View File

@ -0,0 +1,70 @@
import type { MarketState } from '@vegaprotocol/types';
import { hasOperationName } from '.';
import { generateAccounts } from './mocks/generate-accounts';
import { generateCandles } from './mocks/generate-candles';
import { generateChart } from './mocks/generate-chart';
import { generateDealTicketQuery } from './mocks/generate-deal-ticket-query';
import { generateMarket } from './mocks/generate-market';
import { generateOrders } from './mocks/generate-orders';
import { generatePositions } from './mocks/generate-positions';
import { generateTrades } from './mocks/generate-trades';
export const mockTradingPage = (state: MarketState) => {
cy.mockGQL('Market', (req) => {
if (hasOperationName(req, 'Market')) {
req.reply({
body: {
data: generateMarket({
market: {
name: `${state.toUpperCase()} MARKET`,
},
}),
},
});
}
if (hasOperationName(req, 'Orders')) {
req.reply({
body: { data: generateOrders() },
});
}
if (hasOperationName(req, 'Accounts')) {
req.reply({
body: {
data: generateAccounts(),
},
});
}
if (hasOperationName(req, 'Positions')) {
req.reply({
body: { data: generatePositions() },
});
}
if (hasOperationName(req, 'DealTicketQuery')) {
req.reply({
body: { data: generateDealTicketQuery({ market: { state } }) },
});
}
if (hasOperationName(req, 'Trades')) {
req.reply({
body: { data: generateTrades() },
});
}
if (hasOperationName(req, 'Chart')) {
req.reply({
body: { data: generateChart() },
});
}
if (hasOperationName(req, 'Candles')) {
req.reply({
body: { data: generateCandles() },
});
}
});
};

View File

@ -0,0 +1,13 @@
export const connectVegaWallet = () => {
const form = 'rest-connector-form';
const manageVegaBtn = 'manage-vega-wallet';
const walletName = Cypress.env('TRADING_TEST_VEGA_WALLET_NAME');
const walletPassphrase = Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE');
cy.getByTestId('connect-vega-wallet').click();
cy.getByTestId('connectors-list').find('button').click();
cy.getByTestId(form).find('#wallet').click().type(walletName);
cy.getByTestId(form).find('#passphrase').click().type(walletPassphrase);
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId(manageVegaBtn).should('exist');
};

View File

@ -1,69 +0,0 @@
export default class VegaWallet {
connectVegaBtn = 'connect-vega-wallet';
walletConnectors = 'connectors-list';
walletForm = 'rest-connector-form';
selectPublicKeyBtn = 'select-keypair-button';
disconnectAllKeysBtn = 'disconnect';
walletInputError = 'input-wallet-error';
walletFormError = 'form-error';
inputError = 'input-error-text';
connectNetworkBtn = 'connect-network';
openVegaWalletConnectDialog() {
this.clickOnWalletConnectDialog();
cy.contains('Connects using REST to a running Vega wallet service');
cy.getByTestId(this.walletConnectors).find('button').click();
}
fillInWalletForm(walletName: string, walletPassphrase: string) {
cy.getByTestId(this.walletForm)
.find('#wallet')
.click({ force: true })
.type(walletName);
cy.getByTestId(this.walletForm)
.find('#passphrase')
.click({ force: true })
.type(walletPassphrase);
}
clickConnectVegaWallet() {
cy.getByTestId(this.walletForm)
.find('button[type=submit]')
.click({ force: true });
}
validateWalletNotRunningError() {
cy.getByTestId(this.walletFormError).should(
'have.text',
'Authentication failed'
);
}
validateWalletErrorFieldsDisplayed() {
cy.getByTestId(this.walletInputError).should('have.text', 'Required');
cy.getByTestId(this.inputError).should('have.text', 'Required');
}
validatePublicKeyDisplayed(expectedTruncatedKey: string) {
cy.getByTestId(this.connectVegaBtn).should(
'have.text',
expectedTruncatedKey
);
}
validateWalletConnected() {
cy.getByTestId(this.connectVegaBtn).should('contain.text', '…');
}
selectPublicKey() {
cy.getByTestId(this.selectPublicKeyBtn).first().click();
}
clickOnWalletConnectDialog() {
cy.getByTestId(this.connectVegaBtn).click({ force: true });
}
clickDisconnectAllKeys() {
cy.getByTestId(this.disconnectAllKeysBtn).click();
}
}

View File

@ -47,7 +47,11 @@ export const GridTabs = ({ children }: GridTabsProps) => {
{Children.map(children, (child) => {
if (!isValidElement(child)) return null;
return (
<Tabs.Content value={child.props.id} className="h-full">
<Tabs.Content
value={child.props.id}
className="h-full"
data-testid={`tab-${child.props.id}`}
>
{child.props.children}
</Tabs.Content>
);
@ -63,6 +67,6 @@ interface GridTabProps {
name: string;
}
export const GridTab = ({ children }: GridTabProps) => {
export const GridTab = ({ id, children }: GridTabProps) => {
return <div>{children}</div>;
};

View File

@ -27,7 +27,7 @@ export const VegaWalletConnectButton = ({
<span className="text-ui-small font-mono mr-2">Vega key:</span>
)}
<button
data-testid="connect-vega-wallet"
data-testid={isConnected ? 'manage-vega-wallet' : 'connect-vega-wallet'}
onClick={handleClick}
className="ml-auto inline-block text-ui-small font-mono hover:underline"
>

View File

@ -54,7 +54,7 @@ export const Web3Content = ({
const { isActive, error, connector, chainId } = useWeb3React();
useEffect(() => {
if (connector?.connectEagerly) {
if (connector?.connectEagerly && !('Cypress' in window)) {
connector.connectEagerly();
}
}, [connector]);

View File

@ -63,8 +63,12 @@ const MarketPage = ({ id }: { id?: string }) => {
const marketId =
id || (Array.isArray(query.marketId) ? query.marketId[0] : query.marketId);
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
const yTimestamp = new Date(yesterday * 1000).toISOString();
// Cache timestamp for yesterday to prevent full unmount of market page when
// a rerender occurs
const [yTimestamp] = useState(() => {
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
return new Date(yesterday * 1000).toISOString();
});
if (!marketId) {
return (

View File

@ -20,7 +20,7 @@ const Portfolio = () => {
{t('Filters')}
</h2>
</aside>
<section>
<section data-testid="portfolio-grid">
<GridTabs>
<GridTab id="positions" name={t('Positions')}>
<div className={tabClassName}>

View File

@ -64,7 +64,7 @@ export const WithdrawPageContainer = ({
}
const hasIncompleteWithdrawals = data.party?.withdrawals?.some(
(w) => w.txHash
(w) => w.txHash === null
);
return (
@ -73,7 +73,10 @@ export const WithdrawPageContainer = ({
<p className="mb-12">
{t('You have incomplete withdrawals.')}{' '}
<Link href="/portfolio/withdrawals">
<a className="underline">
<a
className="underline"
data-testid="complete-withdrawals-prompt"
>
{t('Click here to finish withdrawal')}
</a>
</Link>

View File

@ -11,7 +11,10 @@ const Withdrawals = () => {
<div className="h-full grid grid grid-rows-[min-content,1fr]">
<header className="flex justify-between p-24">
<h1 className="text-h3">{t('Withdrawals')}</h1>
<AnchorButton href="/portfolio/withdraw">
<AnchorButton
href="/portfolio/withdraw"
data-testid="start-withdrawal"
>
{t('Start withdrawal')}
</AnchorButton>
</header>

View File

@ -26,7 +26,10 @@ export const SelectMarketList = ({
const boldUnderlineClassNames =
'px-8 underline font-sans text-base leading-9 font-bold tracking-tight decoration-solid text-ui light:hover:text-black/80 dark:hover:text-white/80';
return (
<div className="max-h-[40rem] overflow-x-auto">
<div
className="max-h-[40rem] overflow-x-auto"
data-testid="select-market-list"
>
<table className="relative h-full min-w-full whitespace-nowrap">
<thead className="sticky top-0 z-10 dark:bg-black bg-white">
<tr>

View File

@ -60,11 +60,7 @@ export function RestConnectorForm({
autoFocus={true}
/>
{errors.wallet?.message && (
<InputError
data-testid="input-wallet-error"
intent="danger"
className="mt-4"
>
<InputError intent="danger" className="mt-4">
{errors.wallet.message}
</InputError>
)}