Task/229 Stub api in trading e2e (#211)

* add fixture for markets query

* stub graphql requests

* re-add assertion for tx hash, stub command/sync requests

* refactor to get tests to run with trading page mocked queries

* add test wallet credentials

* split up markets page from trading page

* add portfolio page feature, add market page scenarios

* move hasOperationName helper to support/index

* fix home-page.feature

* fix missing feature step

* Minor changes to BDD steps

* Use in object syntax to get better type safety on hasOperationName helper function

* remove bypass placing orders env var and usage in tests

* use UI_Trading_Test wallet publick key in command/sync mock

* move public key to cypress env

* replace fixtures with generator functions

* colocate query generators with queries

* add custom commands, add index files

* fix dodgy merge, remove duplicate market page feature

* make tsconfig for cypress lib match

* update tsconfig for explorer e2e so commands using merge work

* revert trading step to js

Co-authored-by: Joe <joe@vega.xyz>
This commit is contained in:
Matthew Russell 2022-04-12 04:04:26 -07:00 committed by GitHub
parent 3db9546266
commit 04872522d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 755 additions and 260 deletions

View File

@ -3,8 +3,16 @@
"compilerOptions": { "compilerOptions": {
"sourceMap": false, "sourceMap": false,
"outDir": "../../dist/out-tsc", "outDir": "../../dist/out-tsc",
"types": ["cypress", "node"],
"allowJs": true, "allowJs": true,
"types": ["cypress", "node"] "esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}, },
"include": ["src/**/*.ts", "src/**/*.js"] "include": ["src/**/*.ts", "src/**/*.js"]
} }

View File

@ -14,7 +14,7 @@
"chromeWebSecurity": false, "chromeWebSecurity": false,
"projectId": "et4snf", "projectId": "et4snf",
"env": { "env": {
"bypassPlacingOrders": true, "vegaPublicKey": "47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278",
"tsConfig": "tsconfig.json", "tsConfig": "tsconfig.json",
"TAGS": "not @todo and not @ignore and not @manual" "TAGS": "not @todo and not @ignore and not @manual"
} }

View File

@ -1,4 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io"
}

View File

@ -1,11 +1,8 @@
Feature: Market orders Feature: Deal ticket
Scenario Outline: Successfull market buy orders Scenario Outline: Successfull market buy orders
Given I am on the homepage Given I am on the trading page for an active market
And I navigate to markets page
When I click on active market
And I connect to Vega Wallet And I connect to Vega Wallet
And place a buy '<marketOrderType>' market order When I place a buy '<marketOrderType>' market order
Then order request is sent Then order request is sent
Examples: Examples:
@ -14,11 +11,9 @@ Feature: Market orders
| IOC | | IOC |
Scenario Outline: Successfull Limit buy orders Scenario Outline: Successfull Limit buy orders
Given I am on the homepage Given I am on the trading page for an active market
And I navigate to markets page
When I click on active market
And I connect to Vega Wallet And I connect to Vega Wallet
And place a buy '<limitOrderType>' limit order When I place a buy '<limitOrderType>' limit order
Then order request is sent Then order request is sent
Examples: Examples:
@ -30,11 +25,9 @@ Feature: Market orders
| GFN | | GFN |
Scenario Outline: Successfull market sell order Scenario Outline: Successfull market sell order
Given I am on the homepage Given I am on the trading page for an active market
And I navigate to markets page
When I click on active market
And I connect to Vega Wallet And I connect to Vega Wallet
And place a sell '<marketOrderType>' market order When I place a sell '<marketOrderType>' market order
Then order request is sent Then order request is sent
Examples: Examples:
@ -43,11 +36,10 @@ Feature: Market orders
| IOC | | IOC |
Scenario Outline: Successfull limit sell order Scenario Outline: Successfull limit sell order
Given I am on the homepage Given I am on the trading page for an active market
And I navigate to markets page
When I click on active market
And I connect to Vega Wallet And I connect to Vega Wallet
And place a sell '<limitOrderType>' limit order When I place a sell '<limitOrderType>' limit order
Then order request is sent
Examples: Examples:
| limitOrderType | | limitOrderType |
@ -66,27 +58,20 @@ Feature: Market orders
And place a buy 'FOK' market order And place a buy 'FOK' market order
Then error message for insufficient funds is displayed Then error message for insufficient funds is displayed
@ignore
Scenario: Unable to order because market is suspended Scenario: Unable to order because market is suspended
Given I am on the homepage Given I am on the trading page for a suspended market
And I navigate to markets page
When I click on suspended market
And I connect to Vega Wallet And I connect to Vega Wallet
Then place order button is disabled Then place order button is disabled
And "Market is currently suspended" error is shown And "Market is currently suspended" error is shown
Scenario: Unable to order because wallet is not connected Scenario: Unable to order because wallet is not connected
Given I am on the homepage Given I am on the trading page for an active market
And I navigate to markets page
When I click on active market
Then place order button is disabled Then place order button is disabled
And "No public key selected" error is shown And "No public key selected" error is shown
@ignore @ignore
Scenario: Unsuccessfull because quantity is 0 Scenario: Unsuccessfull because quantity is 0
Given I am on the homepage Given I am on the trading page for an active market
And I navigate to markets page
When I click on active market
And I connect to Vega Wallet And I connect to Vega Wallet
And place a buy 'FOK' market order with amount of 0 And place a buy 'FOK' market order with amount of 0
Then Order rejected by wallet error shown containing text "must be positive" Then Order rejected by wallet error shown containing text "must be positive"

View File

@ -7,6 +7,10 @@ Feature: Home page
Given I am on the homepage Given I am on the homepage
And I navigate to portfolio page And I navigate to portfolio page
Scenario: Visit Markets page
Given I am on the homepage
And I navigate to markets page
Scenario: Unable to connect Vega wallet with incorrect credentials Scenario: Unable to connect Vega wallet with incorrect credentials
Given I am on the homepage Given I am on the homepage
When I try to connect Vega wallet with incorrect details When I try to connect Vega wallet with incorrect details

View File

@ -1,6 +0,0 @@
Feature: Market Page
Scenario: Market table displayed on market page
Given I am on the homepage
And I navigate to markets page
Then the market table is displayed

View File

@ -0,0 +1,17 @@
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
When I click on an active market
Then I am on the trading page for an active market
Scenario: Select suspended market
Given I am on the markets page
When I click on a suspended market
Then I am on the trading page for a suspended market

View File

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

View File

@ -1 +0,0 @@
export const getGreeting = () => cy.get('h1');

View File

@ -1,17 +1,11 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import '@vegaprotocol/cypress'; import '@vegaprotocol/cypress';
import type { CyHttpMessages } from 'cypress/types/net-stubbing';
// Utility to match GraphQL mutation based on the operation name
export const hasOperationName = (
req: CyHttpMessages.IncomingHttpRequest,
operationName: string
) => {
const { body } = req;
return 'operationName' in body && body.operationName === operationName;
};

View File

@ -1,56 +1,16 @@
export default class BasePage { export default class BasePage {
porfolioUrl = '/portfolio'; porfolioUrl = '/portfolio';
marketsUrl = '/markets'; marketsUrl = '/markets';
connectVegaBtn = 'connect-vega-wallet';
walletConnectors = 'connectors-list';
walletForm = 'rest-connector-form';
walletInputError = 'input-wallet-error';
walletFormError = 'form-error';
inputError = 'input-error-text';
navigateToPortfolio() { navigateToPortfolio() {
cy.get(`a[href='${this.porfolioUrl}']`).click(); cy.get(`a[href='${this.porfolioUrl}']`).should('be.visible').click();
cy.url().should('include', '/portfolio');
cy.getByTestId('portfolio');
} }
navigateToMarkets() { navigateToMarkets() {
cy.get(`a[href='${this.marketsUrl}']`) cy.get(`a[href='${this.marketsUrl}']`).should('be.visible').click();
.should('be.visible')
.click({ force: true });
cy.url().should('include', '/markets'); cy.url().should('include', '/markets');
} cy.getByTestId('markets');
navigateToConnectVegaWallet() {
cy.getByTestId(this.connectVegaBtn).click();
cy.contains('Connects using REST to a running Vega wallet service');
cy.getByTestId(this.walletConnectors).find('button').click();
}
fillInWalletForm(walletName, walletPassphrase) {
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');
} }
} }

View File

@ -7,15 +7,10 @@ export default class MarketPage extends BasePage {
'tradableInstrument.instrument.product.settlementAsset.symbol'; 'tradableInstrument.instrument.product.settlementAsset.symbol';
marketRowPrices = 'flash-cell'; marketRowPrices = 'flash-cell';
marketRowDescription = 'name'; marketRowDescription = 'name';
chartTab = 'chart';
ticketTab = 'ticket'; validateMarketsAreDisplayed() {
orderbookTab = 'orderbook'; cy.get('.ag-root-wrapper').should('be.visible');
ordersTab = 'orders'; }
positionsTab = 'positions';
collateralTab = 'collateral';
tradesTab = 'trades';
completedTrades = 'market-trades';
orderBookTab = 'orderbook';
validateMarketTableDisplayed() { validateMarketTableDisplayed() {
const expectedMarketHeaders = [ const expectedMarketHeaders = [
@ -58,39 +53,7 @@ export default class MarketPage extends BasePage {
); );
} }
validateCompletedTradesDisplayed() { clickOnMarket(text) {
cy.getByTestId(this.completedTrades).should('not.be.empty'); cy.contains(text).click();
}
clickOnMarket(marketText) {
cy.contains(marketText).click();
}
clickOnActiveMarket() {
cy.contains('Active', { timeout: 8000 }).click({ force: true });
}
clickOnTopMarketRow() {
cy.get('[col-id="data"]').eq(1).click();
}
clickOnOrdersTab() {
cy.getByTestId(this.ordersTab).click();
}
clickOnTicketTab() {
cy.getByTestId(this.ticketTab).click();
}
clickOnCollateralTab() {
cy.getByTestId(this.collateralTab).click();
}
clickOnTradesTab() {
cy.getByTestId(this.tradesTab).click();
}
clickOrderBookTab() {
cy.getByTestId(this.orderBookTab).click();
} }
} }

View File

@ -0,0 +1,3 @@
import BasePage from './base-page';
export default class PortfolioPage extends BasePage {}

View File

@ -0,0 +1,33 @@
import BasePage from './base-page';
export default class TradingPage extends BasePage {
chartTab = 'chart';
ticketTab = 'ticket';
orderbookTab = 'orderbook';
ordersTab = 'orders';
positionsTab = 'positions';
collateralTab = 'collateral';
tradesTab = 'trades';
completedTrades = 'market-trades';
orderBookTab = 'orderbook';
clickOnOrdersTab() {
cy.getByTestId(this.ordersTab).click();
}
clickOnTicketTab() {
cy.getByTestId(this.ticketTab).click();
}
clickOnCollateralTab() {
cy.getByTestId(this.collateralTab).click();
}
clickOnTradesTab() {
cy.getByTestId(this.tradesTab).click();
}
clickOrderBookTab() {
cy.getByTestId(this.orderBookTab).click();
}
}

View File

@ -1,27 +0,0 @@
import { Given, When } from 'cypress-cucumber-preprocessor/steps';
import MarketsPage from '../pages/markets-page';
import DealTicketPage from '../pages/deal-ticket-page';
const marketsPage = new MarketsPage();
const dealTicketPage = new DealTicketPage();
Given('I am on the homepage', () => {
cy.visit('/');
});
Given('I navigate to markets page', () => {
marketsPage.navigateToMarkets();
});
Given('I navigate to portfolio page', () => {
marketsPage.navigateToPortfolio();
});
When('I connect to Vega Wallet', () => {
marketsPage.navigateToConnectVegaWallet();
marketsPage.fillInWalletForm(
'UI_Trading_Test',
Cypress.env('tradingWalletPassphrase')
);
marketsPage.clickConnectVegaWallet();
dealTicketPage.reloadPageIfPublicKeyErrorDisplayed();
});

View File

@ -0,0 +1,5 @@
import { Given } from 'cypress-cucumber-preprocessor/steps';
Given('I am on the homepage', () => {
cy.visit('/');
});

View File

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

View File

@ -1,22 +1,23 @@
import { Then, When } from 'cypress-cucumber-preprocessor/steps'; import { Then, When } from 'cypress-cucumber-preprocessor/steps';
import MarketsPage from '../pages/markets-page'; import VegaWallet from '../vega-wallet';
const marketsPage = new MarketsPage();
const vegaWallet = new VegaWallet();
When('I try to connect Vega wallet with incorrect details', () => { When('I try to connect Vega wallet with incorrect details', () => {
marketsPage.navigateToConnectVegaWallet(); vegaWallet.openVegaWalletConnectDialog();
marketsPage.fillInWalletForm('name', 'wrong passphrase'); vegaWallet.fillInWalletForm('name', 'wrong passphrase');
marketsPage.clickConnectVegaWallet(); vegaWallet.clickConnectVegaWallet();
}); });
When('I try to connect Vega wallet with blank fields', () => { When('I try to connect Vega wallet with blank fields', () => {
marketsPage.navigateToConnectVegaWallet(); vegaWallet.openVegaWalletConnectDialog();
marketsPage.clickConnectVegaWallet(); vegaWallet.clickConnectVegaWallet();
}); });
Then('wallet not running error message is displayed', () => { Then('wallet not running error message is displayed', () => {
marketsPage.validateWalletNotRunningError(); vegaWallet.validateWalletNotRunningError();
}); });
Then('wallet field validation errors are shown', () => { Then('wallet field validation errors are shown', () => {
marketsPage.validateWalletErrorFieldsDisplayed(); vegaWallet.validateWalletErrorFieldsDisplayed();
}); });

View File

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

View File

@ -1,23 +1,47 @@
import { When, Then } from 'cypress-cucumber-preprocessor/steps'; import { And, Given, Then, When } from 'cypress-cucumber-preprocessor/steps';
import { hasOperationName } from '..';
import MarketsPage from '../pages/markets-page'; import MarketsPage from '../pages/markets-page';
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { generateMarkets } from '../../../../../libs/market-list/src/__tests__/generate-markets';
const marketsPage = new MarketsPage(); const marketsPage = new MarketsPage();
When('I click on market for {string}', (marketText) => { const mockMarkets = () => {
marketsPage.clickOnMarket(marketText); cy.mockGQL('Markets', (req) => {
}); if (hasOperationName(req, 'Markets')) {
req.reply({
When('I click on active market', () => { body: { data: generateMarkets() },
if (Cypress.env('bypassPlacingOrders' != true)) { });
marketsPage.clickOnMarket('Active');
} else {
marketsPage.clickOnTopMarketRow();
} }
});
};
beforeEach(() => {
mockMarkets();
}); });
When('I click on suspended market', () => { Then('I navigate to markets page', () => {
marketsPage.clickOnMarket('Suspended'); marketsPage.navigateToMarkets();
}); });
Then('the market table is displayed', () => { Given('I am on the markets page', () => {
cy.visit('/markets');
cy.wait('@Markets');
});
Then('I can view markets', () => {
cy.wait('@Markets');
marketsPage.validateMarketsAreDisplayed();
});
And('the market table is displayed', () => {
marketsPage.validateMarketTableDisplayed(); marketsPage.validateMarketTableDisplayed();
}); });
When('I click on an active market', () => {
marketsPage.clickOnMarket('Active');
});
When('I click on a suspended market', () => {
marketsPage.clickOnMarket('Suspended');
});

View File

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

View File

@ -0,0 +1,68 @@
import { Given } from 'cypress-cucumber-preprocessor/steps';
import { hasOperationName } from '..';
import { MarketState } from '@vegaprotocol/types';
/* eslint-disable @nrwl/nx/enforce-module-boundaries */
import { generateTrades } from '../../../../../libs/trades/src/__tests__';
import {
generateChart,
generateCandles,
} from '../../../../../libs/chart/src/__tests__';
import { generateDealTicketQuery } from '../../../../../libs/deal-ticket/src/__tests__';
import { generateMarket } from '../../../../trading/pages/markets/__tests__';
/* eslint-enable @nrwl/nx/enforce-module-boundaries */
const mockMarket = (state) => {
cy.mockGQL('Market', (req) => {
if (hasOperationName(req, 'Market')) {
req.reply({
body: {
data: generateMarket({
market: {
name: `${state.toUpperCase()} MARKET`,
},
}),
},
});
}
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() },
});
}
});
};
Given('I am on the trading page for an active market', () => {
mockMarket(MarketState.Active);
cy.visit('/markets/market-id');
cy.wait('@Market');
cy.contains('Market: 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('Market: SUSPENDED MARKET');
});

View File

@ -0,0 +1,30 @@
import { When } 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(
'UI_Trading_Test',
Cypress.env('tradingWalletPassphrase')
);
vegaWallet.clickConnectVegaWallet();
});

View File

@ -1,5 +1,4 @@
import BasePage from './base-page'; export default class DealTicket {
export default class DealTicketPage extends BasePage {
marketOrderType = 'order-type-TYPE_MARKET'; marketOrderType = 'order-type-TYPE_MARKET';
limitOrderType = 'order-type-TYPE_LIMIT'; limitOrderType = 'order-type-TYPE_LIMIT';
buyOrder = 'order-side-SIDE_BUY'; buyOrder = 'order-side-SIDE_BUY';
@ -53,8 +52,7 @@ export default class DealTicketPage extends BasePage {
); );
cy.getByTestId(this.orderTransactionHash) cy.getByTestId(this.orderTransactionHash)
.invoke('text') .invoke('text')
.should('contain', 'Tx hash: ') .should('contain', 'Tx hash: test-tx-hash');
.and('have.length.above', 64);
} }
verifyOrderFailedInsufficientFunds() { verifyOrderFailedInsufficientFunds() {
@ -65,9 +63,8 @@ export default class DealTicketPage extends BasePage {
} }
clickPlaceOrder() { clickPlaceOrder() {
if (Cypress.env('bypassPlacingOrders' != true)) {
cy.getByTestId(this.placeOrderBtn).click(); cy.getByTestId(this.placeOrderBtn).click();
} cy.contains('Awaiting network confirmation');
} }
verifyPlaceOrderBtnDisabled() { verifyPlaceOrderBtnDisabled() {
@ -75,7 +72,10 @@ export default class DealTicketPage extends BasePage {
} }
verifySubmitBtnErrorText(expectedText) { verifySubmitBtnErrorText(expectedText) {
cy.getByTestId(this.inputError).should('have.text', expectedText); cy.getByTestId('dealticket-error-message').should(
'have.text',
expectedText
);
} }
verifyOrderRejected(errorMsg) { verifyOrderRejected(errorMsg) {

View File

@ -0,0 +1,43 @@
export default class VegaWallet {
connectVegaBtn = 'connect-vega-wallet';
walletConnectors = 'connectors-list';
walletForm = 'rest-connector-form';
walletInputError = 'input-wallet-error';
walletFormError = 'form-error';
inputError = 'input-error-text';
openVegaWalletConnectDialog() {
cy.getByTestId(this.connectVegaBtn).click();
cy.contains('Connects using REST to a running Vega wallet service');
cy.getByTestId(this.walletConnectors).find('button').click();
}
fillInWalletForm(walletName, walletPassphrase) {
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');
}
}

View File

@ -4,7 +4,15 @@
"sourceMap": false, "sourceMap": false,
"outDir": "../../dist/out-tsc", "outDir": "../../dist/out-tsc",
"allowJs": true, "allowJs": true,
"types": ["cypress", "node"] "types": ["cypress", "node"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}, },
"include": ["src/**/*.ts", "src/**/*.js"] "include": ["src/**/*.ts", "src/**/*.js"]
} }

View File

@ -60,7 +60,7 @@ function VegaTradingApp({ Component, pageProps }: AppProps) {
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" /> <ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
</div> </div>
</div> </div>
<main> <main data-testid={pageProps.page}>
<Component {...pageProps} /> <Component {...pageProps} />
</main> </main>
<VegaConnectDialog <VegaConnectDialog

View File

@ -22,4 +22,8 @@ export function Index() {
); );
} }
Index.getInitialProps = () => ({
page: 'home',
});
export default Index; export default Index;

View File

@ -60,6 +60,10 @@ const MarketPage = () => {
); );
}; };
MarketPage.getInitialProps = () => ({
page: 'market',
});
export default MarketPage; export default MarketPage;
const useWindowSize = () => { const useWindowSize = () => {

View File

@ -0,0 +1,15 @@
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type { Market } from '../__generated__/Market';
export const generateMarket = (override?: PartialDeep<Market>): Market => {
const defaultResult = {
market: {
id: 'market-id',
name: 'MARKET NAME',
__typename: 'Market',
},
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1 @@
export * from './generate-market';

View File

@ -4,4 +4,8 @@ const Markets = () => {
return <MarketsContainer />; return <MarketsContainer />;
}; };
Markets.getInitialProps = () => ({
page: 'markets',
});
export default Markets; export default Markets;

View File

@ -31,4 +31,8 @@ const Deposit = () => {
); );
}; };
Deposit.getInitialProps = () => ({
page: 'deposit',
});
export default Deposit; export default Deposit;

View File

@ -5,8 +5,8 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
const Portfolio = () => { const Portfolio = () => {
const { keypair } = useVegaWallet(); const { keypair } = useVegaWallet();
return ( return (
<div> <div className="p-24">
<h1>{t('Portfolio')}</h1> <h1 className="text-h3 mb-12">{t('Portfolio')}</h1>
{keypair && <p>{t(`Keypair: ${keypair.name} ${keypair.pub}`)}</p>} {keypair && <p>{t(`Keypair: ${keypair.name} ${keypair.pub}`)}</p>}
<div className="flex gap-4"> <div className="flex gap-4">
<AnchorButton href="/portfolio/deposit">{t('Deposit')}</AnchorButton> <AnchorButton href="/portfolio/deposit">{t('Deposit')}</AnchorButton>
@ -18,4 +18,8 @@ const Portfolio = () => {
); );
}; };
Portfolio.getInitialProps = () => ({
page: 'portfolio',
});
export default Portfolio; export default Portfolio;

View File

@ -0,0 +1,56 @@
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type {
Candles,
Candles_market_candles,
} from '../lib/__generated__/Candles';
export const generateCandles = (override?: PartialDeep<Candles>): Candles => {
const candles: Candles_market_candles[] = [
{
datetime: '2022-04-06T09:15:00Z',
high: '17481092',
low: '17403651',
open: '17458833',
close: '17446470',
volume: '82721',
__typename: 'Candle',
},
{
datetime: '2022-04-06T09:30:00Z',
high: '17491202',
low: '17361138',
open: '17446470',
close: '17367174',
volume: '62637',
__typename: 'Candle',
},
{
datetime: '2022-04-06T09:45:00Z',
high: '17424522',
low: '17337719',
open: '17367174',
close: '17376455',
volume: '60259',
__typename: 'Candle',
},
];
const defaultResult = {
market: {
id: 'market-id',
decimalPlaces: 5,
tradableInstrument: {
instrument: {
id: '',
name: 'Apple Monthly (30 Jun 2022)',
code: 'AAPL.MF21',
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles,
__typename: 'Market',
},
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1,27 @@
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type {
Chart,
Chart_market_data_priceMonitoringBounds,
} from '../lib/__generated__/Chart';
export const generateChart = (override?: PartialDeep<Chart>): Chart => {
const priceMonitoringBound: Chart_market_data_priceMonitoringBounds = {
minValidPrice: '16256291',
maxValidPrice: '18296869',
referencePrice: '17247489',
__typename: 'PriceMonitoringBounds',
};
const defaultResult = {
market: {
decimalPlaces: 5,
data: {
priceMonitoringBounds: [priceMonitoringBound],
__typename: 'MarketData',
},
__typename: 'Market',
},
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1,2 @@
export * from './generate-candles';
export * from './generate-chart';

View File

@ -1,5 +1,9 @@
import { addGetTestIdcommand } from './lib/commands/get-by-test-id'; import { addGetTestIdcommand } from './lib/commands/get-by-test-id';
import { addMockGQLCommand } from './lib/commands/mock-gql';
import { addMockVegaWalletCommands } from './lib/commands/mock-vega-wallet';
import { addSlackCommand } from './lib/commands/slack'; import { addSlackCommand } from './lib/commands/slack';
addGetTestIdcommand(); addGetTestIdcommand();
addSlackCommand(); addSlackCommand();
addMockGQLCommand();
addMockVegaWalletCommands();

View File

@ -0,0 +1,21 @@
import type { RouteHandler } from 'cypress/types/net-stubbing';
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
mockGQL(alias: string, handler: RouteHandler): void;
}
}
export function addMockGQLCommand() {
Cypress.Commands.add(
// @ts-ignore - ignoring Cypress type error which gets resolved when Cypress uses the command
'mockGQL',
(alias: string, handler: RouteHandler) => {
cy.intercept('POST', 'https://lb.testnet.vega.xyz/query', handler).as(
alias
);
}
);
}

View File

@ -0,0 +1,45 @@
import merge from 'lodash/merge';
import type { TransactionResponse } from '@vegaprotocol/vegawallet-service-api-client';
import type { PartialDeep } from 'type-fest';
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
mockVegaCommandSync(txHash: string, signature: string): void;
}
}
export function addMockVegaWalletCommands() {
Cypress.Commands.add(
// @ts-ignore - ignoring Cypress type error which gets resolved when Cypress uses the command
'mockVegaCommandSync',
(override?: PartialDeep<TransactionResponse>) => {
const defaultTransactionResponse = {
txHash: 'tx-hash',
tx: {
input_data:
'CPe6vpiqsPqxDBDC1w7KPkoKQGE4Y2M0NjUwMjhiMGY4OTM4YTYzZTEzNDViYzM2ODc3ZWRmODg4MjNmOWU0ZmI4ZDRlN2VkMmFlMzAwNzA3ZTMYASABKAM4Ag==',
signature: {
// sig value needs to be 'real' so sigToId function doesn't error out
value: 'signature',
algo: 'vega/ed25519',
version: 1,
},
From: {
PubKey: Cypress.env('vegaPublicKey'),
},
version: 2,
pow: {
tid: '0CEEC2FDFDB5D68BC0C1E2640440E4AA11E49986CB2929E0F3572E16CB7DC59C',
nonce: 23525,
},
},
};
cy.intercept('POST', 'http://localhost:1789/api/v1/command/sync', {
body: merge(defaultTransactionResponse, override),
}).as('VegaCommandSync');
}
);
}

View File

@ -11,8 +11,13 @@
} }
], ],
"compilerOptions": { "compilerOptions": {
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"strict": true, "strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true
} }

View File

@ -0,0 +1,37 @@
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type { DealTicketQuery } from '../__generated__/DealTicketQuery';
export const generateDealTicketQuery = (
override?: PartialDeep<DealTicketQuery>
): DealTicketQuery => {
const defaultResult: DealTicketQuery = {
market: {
id: 'market-id',
decimalPlaces: 2,
state: MarketState.Active,
tradingMode: MarketTradingMode.Continuous,
tradableInstrument: {
instrument: {
product: {
quoteName: 'BTC',
__typename: 'Future',
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
depth: {
__typename: 'MarketDepth',
lastTrade: {
__typename: 'Trade',
price: '100',
},
},
__typename: 'Market',
},
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1 @@
export * from './generate-deal-ticket-query';

View File

@ -79,7 +79,11 @@ export const SubmitButton = ({
> >
{transactionStatus === 'pending' ? t('Pending...') : t('Place order')} {transactionStatus === 'pending' ? t('Pending...') : t('Place order')}
</Button> </Button>
{invalidText && <InputError className="mb-8">{invalidText}</InputError>} {invalidText && (
<InputError className="mb-8" data-testid="dealticket-error-message">
{invalidText}
</InputError>
)}
</> </>
); );
}; };

View File

@ -0,0 +1,78 @@
import merge from 'lodash/merge';
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
import type { PartialDeep } from 'type-fest';
import type { Markets, Markets_markets } from '../lib/__generated__/Markets';
export const generateMarkets = (override?: PartialDeep<Markets>): Markets => {
const markets: Markets_markets[] = [
{
id: 'market-id',
name: 'ACTIVE MARKET',
decimalPlaces: 5,
data: {
market: {
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
state: MarketState.Active,
tradingMode: MarketTradingMode.Continuous,
__typename: 'Market',
},
bestBidPrice: '0',
bestOfferPrice: '0',
markPrice: '4612690058',
__typename: 'MarketData',
},
tradableInstrument: {
instrument: {
code: 'BTCUSD.MF21',
product: {
settlementAsset: {
symbol: 'tDAI',
__typename: 'Asset',
},
__typename: 'Future',
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
__typename: 'Market',
},
{
id: 'test-market-suspended',
name: 'SUSPENDED MARKET',
decimalPlaces: 2,
data: {
market: {
id: '34d95e10faa00c21d19d382d6d7e6fc9722a96985369f0caec041b0f44b775ed',
state: MarketState.Suspended,
tradingMode: MarketTradingMode.Continuous,
__typename: 'Market',
},
bestBidPrice: '0',
bestOfferPrice: '0',
markPrice: '8441',
__typename: 'MarketData',
},
tradableInstrument: {
instrument: {
code: 'SOLUSD',
product: {
settlementAsset: {
symbol: 'XYZalpha',
__typename: 'Asset',
},
__typename: 'Future',
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
__typename: 'Market',
},
];
const defaultResult = {
markets,
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1,53 @@
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type { Trades, Trades_market_trades } from '../lib/__generated__/Trades';
export const generateTrades = (override?: PartialDeep<Trades>): Trades => {
const trades: Trades_market_trades[] = [
{
id: 'FFFFBC80005C517A10ACF481F7E6893769471098E696D0CC407F18134044CB16',
price: '17116898',
size: '24',
createdAt: '2022-04-06T16:19:42.692598951Z',
market: {
id: '0c3c1490db767f926d24fb674b4235a9aa339614915a4ab96cbfc0e1ad83c0ff',
decimalPlaces: 5,
__typename: 'Market',
},
__typename: 'Trade',
},
{
id: 'FFFFB91453AC8F26EDAC223E2FB6C4A61461B1837946B51D943D675FB94FDF72',
price: '17209102',
size: '7',
createdAt: '2022-04-07T06:59:44.835686754Z',
market: {
id: '0c3c1490db767f926d24fb674b4235a9aa339614915a4ab96cbfc0e1ad83c0ff',
decimalPlaces: 5,
__typename: 'Market',
},
__typename: 'Trade',
},
{
id: 'FFFFAD1BF47AA2853E5C375B6B3A62375F62D5B10807583D32EF3119CC455CD1',
price: '17106734',
size: '18',
createdAt: '2022-04-07T17:56:47.997938583Z',
market: {
id: '0c3c1490db767f926d24fb674b4235a9aa339614915a4ab96cbfc0e1ad83c0ff',
decimalPlaces: 5,
__typename: 'Market',
},
__typename: 'Trade',
},
];
const defaultResult = {
market: {
id: 'market-id',
trades,
__typename: 'Market',
},
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1 @@
export * from './generate-trades';

View File

@ -124,6 +124,7 @@
"sass": "1.43.2", "sass": "1.43.2",
"storybook-addon-themes": "^6.1.0", "storybook-addon-themes": "^6.1.0",
"ts-jest": "27.0.5", "ts-jest": "27.0.5",
"type-fest": "^2.12.2",
"typescript": "~4.5.2", "typescript": "~4.5.2",
"url-loader": "^3.0.0" "url-loader": "^3.0.0"
}, },

View File

@ -20171,6 +20171,11 @@ type-fest@^0.8.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
type-fest@^2.12.2:
version "2.12.2"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.12.2.tgz#80a53614e6b9b475eb9077472fb7498dc7aa51d0"
integrity sha512-qt6ylCGpLjZ7AaODxbpyBZSs9fCI9SkL3Z9q2oxMBQhs/uyY+VD8jHA8ULCGmWQJlBgqvO3EJeAngOHD8zQCrQ==
type-is@~1.6.18: type-is@~1.6.18:
version "1.6.18" version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"