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:
parent
72f94d2e6d
commit
9941c9bfaa
@ -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"
|
||||
}
|
||||
|
60
apps/trading-e2e/src/integration/deposit.ts
Normal file
60
apps/trading-e2e/src/integration/deposit.ts
Normal 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');
|
||||
});
|
||||
});
|
@ -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
|
||||
|
78
apps/trading-e2e/src/integration/global.ts
Normal file
78
apps/trading-e2e/src/integration/global.ts
Normal 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');
|
||||
});
|
||||
});
|
@ -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
|
171
apps/trading-e2e/src/integration/home.ts
Normal file
171
apps/trading-e2e/src/integration/home.ts
Normal 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;
|
||||
}
|
@ -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
|
92
apps/trading-e2e/src/integration/markets.ts
Normal file
92
apps/trading-e2e/src/integration/markets.ts
Normal 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');
|
||||
});
|
||||
});
|
@ -1,5 +0,0 @@
|
||||
|
||||
Feature: Portfolio page
|
||||
Scenario: Navigation
|
||||
Given I am on the homepage
|
||||
Then I navigate to portfolio page
|
6
apps/trading-e2e/src/integration/portfolio.ts
Normal file
6
apps/trading-e2e/src/integration/portfolio.ts
Normal file
@ -0,0 +1,6 @@
|
||||
describe('portfolio', () => {
|
||||
it('requires connecting', () => {
|
||||
cy.visit('/portfolio');
|
||||
cy.get('main[data-testid="portfolio"]').should('exist');
|
||||
});
|
||||
});
|
38
apps/trading-e2e/src/integration/trading-accounts.ts
Normal file
38
apps/trading-e2e/src/integration/trading-accounts.ts
Normal 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');
|
||||
});
|
||||
});
|
166
apps/trading-e2e/src/integration/trading-deal-ticket.ts
Normal file
166
apps/trading-e2e/src/integration/trading-deal-ticket.ts
Normal 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'
|
||||
);
|
||||
});
|
||||
});
|
69
apps/trading-e2e/src/integration/trading-orders.ts
Normal file
69
apps/trading-e2e/src/integration/trading-orders.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
@ -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
|
35
apps/trading-e2e/src/integration/trading-positions.ts
Normal file
35
apps/trading-e2e/src/integration/trading-positions.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
34
apps/trading-e2e/src/integration/trading-trades.ts
Normal file
34
apps/trading-e2e/src/integration/trading-trades.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
97
apps/trading-e2e/src/integration/withdraw.ts
Normal file
97
apps/trading-e2e/src/integration/withdraw.ts
Normal 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
|
||||
});
|
@ -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
|
82
apps/trading-e2e/src/integration/withdrawals.ts
Normal file
82
apps/trading-e2e/src/integration/withdrawals.ts
Normal 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
|
||||
});
|
5
apps/trading-e2e/src/support/ethereum-wallet.ts
Normal file
5
apps/trading-e2e/src/support/ethereum-wallet.ts
Normal 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();
|
||||
};
|
@ -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);
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './ethereum-wallet';
|
@ -37,7 +37,7 @@ export const generateCandles = (override?: PartialDeep<Candles>): Candles => {
|
||||
];
|
||||
const defaultResult = {
|
||||
market: {
|
||||
id: 'market-id',
|
||||
id: 'market-0',
|
||||
decimalPlaces: 5,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
|
@ -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: {
|
||||
|
36
apps/trading-e2e/src/support/mocks/generate-deposit-page.ts
Normal file
36
apps/trading-e2e/src/support/mocks/generate-deposit-page.ts
Normal 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);
|
||||
};
|
@ -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: {
|
||||
|
@ -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',
|
||||
},
|
||||
|
@ -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);
|
||||
};
|
@ -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: {
|
||||
|
@ -43,7 +43,7 @@ export const generateTrades = (override?: PartialDeep<Trades>): Trades => {
|
||||
];
|
||||
const defaultResult = {
|
||||
market: {
|
||||
id: 'market-id',
|
||||
id: 'market-0',
|
||||
trades,
|
||||
__typename: 'Market',
|
||||
},
|
||||
|
@ -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);
|
||||
};
|
58
apps/trading-e2e/src/support/mocks/generate-withdrawals.ts
Normal file
58
apps/trading-e2e/src/support/mocks/generate-withdrawals.ts
Normal 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);
|
||||
};
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
@ -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');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
});
|
@ -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);
|
||||
}
|
||||
);
|
@ -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();
|
||||
});
|
@ -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();
|
||||
});
|
@ -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);
|
||||
});
|
@ -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();
|
||||
});
|
@ -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');
|
||||
});
|
@ -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
|
||||
});
|
@ -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);
|
||||
});
|
@ -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);
|
||||
}
|
||||
}
|
@ -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}`;
|
||||
}
|
||||
}
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
70
apps/trading-e2e/src/support/trading.ts
Normal file
70
apps/trading-e2e/src/support/trading.ts
Normal 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() },
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
13
apps/trading-e2e/src/support/vega-wallet.ts
Normal file
13
apps/trading-e2e/src/support/vega-wallet.ts
Normal 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');
|
||||
};
|
@ -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();
|
||||
}
|
||||
}
|
@ -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>;
|
||||
};
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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]);
|
||||
|
@ -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 (
|
||||
|
@ -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}>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
)}
|
||||
|
Loading…
Reference in New Issue
Block a user