feat(e2e): Removed Cypress

This commit is contained in:
Ondrej Hajek
2025-02-26 16:14:48 +01:00
committed by martin
parent fce4f90fd7
commit 17c575d2bf
64 changed files with 45 additions and 5155 deletions

View File

@@ -169,10 +169,6 @@ packages/suite-data/files/translations/master.json
**/build-webextension/**
# cypress download folder
packages/suite-web/e2e/downloads
### suite-native/app/.gitignore
# Expo

4
.gitignore vendored
View File

@@ -133,9 +133,5 @@ packages/suite-data/files/translations/master.json
# @trezor/connect-explorer
**/build-webextension/**
# cypress download folder
packages/suite-web/e2e/downloads
# python
.venv/

View File

@@ -155,8 +155,4 @@ packages/connect-explorer/build-webextension
packages/connect-explorer-theme/style.css
# cypress download folder
packages/suite-web/e2e/downloads
.github

View File

@@ -1,38 +0,0 @@
version: "3.9"
services:
trezor-user-env-unix:
image: ghcr.io/trezor/trezor-user-env:dadc035aa0c121140dba0991ec6abdb786e18406
environment:
- SDL_VIDEODRIVER=dummy
- XDG_RUNTIME_DIR=/var/tmp
network_mode: bridge # this makes docker reuse existing networks
test-run:
image: cypress/included:13.6.4
entrypoint: []
environment:
- CYPRESS_SNAPSHOT=$CYPRESS_SNAPSHOT
- CYPRESS_updateSnapshots=$CYPRESS_updateSnapshots
- CYPRESS_baseUrl=$CYPRESS_baseUrl
- CYPRESS_ASSET_PREFIX=$CYPRESS_ASSET_PREFIX
- CYPRESS_TEST_URLS=$CYPRESS_TEST_URLS
- CYPRESS_USE_TREZOR_USER_ENV_BRIDGE=$CYPRESS_USE_TREZOR_USER_ENV_BRIDGE
- LOCAL_USER_ID=$LOCAL_USER_ID
- TEST_GROUP=$TEST_GROUP
- TRACK_SUITE_URL=$TRACK_SUITE_URL
- ALLOW_RETRY=$ALLOW_RETRY
- CI_JOB_URL=$CI_JOB_URL
- CI_JOB_ID=$CI_JOB_ID
- CI_COMMIT_BRANCH=$CI_COMMIT_BRANCH
- CI_COMMIT_MESSAGE=$CI_COMMIT_MESSAGE
- CI_COMMIT_SHA=$CI_COMMIT_SHA
- FIRMWARE=$FIRMWARE
network_mode: service:trezor-user-env-unix
working_dir: /trezor-suite
command: bash -c "cd packages/suite-web && yarn tsx ./e2e/run_tests.ts --group=$TEST_GROUP"
volumes:
- ../:/trezor-suite
bitcoin-regtest:
image: ghcr.io/trezor/trezor-user-env-regtest # this is a special image that runs regtest and blockbook
depends_on:
- trezor-user-env-unix
network_mode: service:trezor-user-env-unix

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -e
# todo: I am not sure whether locally generated snapshots won't somethimes differ from CI generated, this is to be tested
xhost +
LOCAL_USER_ID=$(id -u "$USER")
export LOCAL_USER_ID
export CYPRESS_SNAPSHOT=1
export CYPRESS_updateSnapshots=1
docker compose -f ./docker/docker-compose.suite-test.yml up --build --abort-on-container-exit

View File

@@ -1,172 +0,0 @@
# @trezor/suite-web e2e tests
@trezor/suite-web uses [Cypress](https://docs.cypress.io/guides/overview/why-cypress.html) to run e2e tests. It also uses [trezor-user-env](https://github.com/trezor/trezor-user-env) which is [daily built](https://gitlab.com/satoshilabs/trezor/trezor-user-env/-/pipelines) into a docker image providing all the necessary instrumentation required to run tests (bridge and emulators).
## Run it locally
_Note: All paths below are relative to the root of trezor-suite repository, if not specified otherwise._
### On Linux
#### Prerequisites
- [Docker](https://docs.docker.com/engine/install/)
#### Steps
1. Run `xhost +` to add yourself to the X access control list.
1. Run `docker/docker-suite-install.sh`.
1. Run `docker/docker-suite-test.sh`.
- A Cypress window should open.
- Wait until the project is built (a warning about "http://localhost:8000/ is not available", should disappear on the retry button click).
1. Start a test by clicking its name in the Cypress window.
- It should open a browser window.
- If the Suite web app is not loading even after two retries. Stop tests, open a new tab, navigate to http://localhost:8000/, refresh the page until the app is loaded. Close the tab and run tests again.
#### Troubleshooting
- `Error while fetching server API version: ('Connection aborted.', FileNotFoundError(2, 'No such file or directory'))`
- On NixOS: Make sure that docker is enabled in your configuration.nix:\
`virtualisation.docker.enable = true;`
- `Error while fetching server API version: ('Connection aborted.', PermissionError(13, 'Permission denied'))` - Check the docker.sock permissions:\
`sudo chmod 666 /var/run/docker.sock`
### On MacOS (Intel)
#### Prerequisites
- [Docker](https://docs.docker.com/desktop/mac/install/)
- [XQuartz](https://www.xquartz.org/) (to share your screen with Docker)
#### Steps
1. Run XQuartz. Wait till it is launched. Leave it running in the background.
1. In XQuartz settings go to Preferences -> Security and enable "Allow connections from network clients".
1. Open a new terminal window (not in XQuartz) and add yourself to the X access control list:
- `xhost +127.0.0.1`
- You will probably need to logout/login after XQuartz installation to have `xhost` command available.
1. Run Docker and go to Preferences -> Resources -> Advanced and increase RAM to at least 4GB. Otherwise, the app during tests does not even load.
1. In terminal window run `docker/docker-suite-install.sh`
1. In the terminal window, set two environment variables:
- `` export HOSTNAME=`hostname` ``
- `export DISPLAY=${HOSTNAME}:0`
1. In terminal window run `docker/docker-suite-test.sh`
- A Cypress window should open.
- Wait until the project is built (a warning about "http://localhost:8000/ is not available", should disappear on the retry button click).
1. Start a test by clicking its name in the Cypress window.
- It should open a browser window.
- If the Suite web app is not loading even after two retries. Stop tests, open a new tab, navigate to http://localhost:8000/, refresh the page until the app is loaded. Close the tab and run tests again.
#### Troubleshooting
- `[...ERROR:browser_main_loop.cc(1434)] Unable to open X display.`
- Make sure the XQuartz app is launched and you can see its terminal.
- Check that environment variables are properly set:
- `echo $HOSTNAME # e.g. name.local`
- `echo $DISPLAY # e.g. name.local:0`
- Do not mix native terminal window with terminal window in your IDE (e.g. Visual Studio Code).
### On MacOS (ARM)
- currently, it is not possible to run E2E tests only within a Docker container on ARM mac. With `Trezor-user-env` run in Docker, it is possible to run `Suite` and `Cypress` locally.
#### Prerequisites
- [Docker](https://docs.docker.com/desktop/mac/install/)
- [XQuartz](https://www.xquartz.org/) (to share your screen with Docker)
- [Trezor user env](https://github.com/trezor/trezor-user-env)
- No other instance of `Suite` or `trezord` service is running
Steps:
1. Run XQuartz. Wait till it is launched. Leave it running in the background.
1. In XQuartz settings go to Preferences -> Security and enable "Allow connections from network clients".
1. Open a new terminal window (not in XQuartz) and add yourself to the X access control list:
- `xhost +127.0.0.1`
- You will probably need to logout/login after XQuartz installation to have `xhost` command available.
1. Run Docker and go to Preferences -> Resources -> Advanced and increase RAM to at least 4GB. Otherwise, the app during tests does not even load.
1. In the terminal window, set two environment variables:
- ``export HOSTNAME=`hostname` ``
- `export DISPLAY=${HOSTNAME}:0`
1. In terminal window, navigate to `trezor-user-env` repo root and run `./run.sh`.
1. In another window, run web `Suite` with `yarn suite:dev`.
1. In a third window, run `npx cypress open --project packages/suite-web/e2e --config 'baseUrl=http://localhost:8000'`.
#### Troubleshooting
- `Cypress could not verify that this server is running ...`
- make sure that the localhost is actually running and is accessible via browser
- `tests fail at the very beginning on screen with "Use trezor here" button`
- make sure that no other instance of `Suite` or `trezord` is running
---
## Notes
### Best practices and established patterns
#### Page objects
There should be no direct getting of elements in the test code. We want that and most other logic operating the app to be abstracted to the page objects located in `packages/suite-web/e2e/support/pageObjects`.
#### Steps
Give the test a structure by using cy.step() blocks.
Example:
```
cy.step('Setup standard wallet with label', () => {...});
```
#### Easily understandable and readable code
If there is a magic constant or hard to understand block of code, put it under a named constant/function.
### Image snapshots
It is possible to run tests with image snapshots to test for visual regressions. To enable snapshots, use env variable:
`CYPRESS_SNAPSHOT=1 docker/docker-suite-test.sh`
When you need to update image snapshots you have 2 options:
- use CI job. This will generate new snapshots in artifacts together with a handy script that updates your snapshots locally. Check the log output.
- use `docker/docker-suite-snapshots.sh`. This does the same as `docker/docker-suite-test.sh`, the only difference is it won't fail on non-matching snapshots but generate new snapshots.
### run_tests script
The [run_tests.js script](../../packages/suite-web/e2e/run_tests.ts)
is the entry point for e2e tests. It:
- picks tests files to be run (see @tags)
- retries tests if needed (see @retry)
- reports tests results
### tags
Each test should be assigned a tag at the top of the test file. These allow you to add more fine-grained control
in run_tests.js.
At the moment, there are the following tags:
- @group\_[string]
- @retry=[number]
#### @group
Assigning a @group allows run_tests.js script to sort the test files into groups and run them in parallel on CI. At the moment these groups exist:
- `@group_metadata`
- `@group_device-management`
- `@group_suite`
- `@group_settings`
#### @retry
If there is a test that you for any reason need to retry if it fails you may provide `@retry=2` tag. In this
case, test will be run 3 times in total and count as failed only if all runs fail.
### Results
There is a tool to track tests runs and their results, temporarily hosted here https://track-suite.herokuapp.com/
Repo here: https://github.com/mroz22/track-suite

View File

@@ -2,6 +2,7 @@
This chapter contains information about tests.
- [e2e @trezor/suite-web](./e2e-suite-web.md)
- [e2e playwright suite](./e2e-playwright-suite.md)
- [e2e playwright contribution guide](./e2e-playwright-contribution-guide.md)
- [e2e @trezor/connect-popup](./e2e-connect-popup.md)
- [regtest](./regtest.md)

View File

@@ -1,266 +0,0 @@
import { defineConfig } from 'cypress';
import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin';
import fs from 'fs';
import path from 'path';
import {
BackendWebsocketServerMock,
DropboxMock,
GoogleMock,
TrezorBridgeMock,
} from '@trezor/e2e-utils';
import * as messages from '@trezor/protobuf/src/messages';
import * as metadataUtils from '@trezor/suite/src/utils/suite/metadata';
import { BridgeTransport } from '@trezor/transport';
import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link';
const mocked = {
bridge: new TrezorBridgeMock(),
dropbox: new DropboxMock(),
google: new GoogleMock(),
};
// const ensureRdpPort = (args: any[]) => {
// const existing = args.find(arg => arg.slice(0, 23) === '--remote-debugging-port');
// if (existing) {
// return Number(existing.split('=')[1]);
// }
// const port = 40000 + Math.round(Math.random() * 25000);
// args.push(`--remote-debugging-port=${port}`);
// return port;
// };
// let port = 0;
let client: any = null;
let blockbook: BackendWebsocketServerMock | undefined;
// // add snapshot plugin
// addMatchImageSnapshotPlugin(on);
// on('before:browser:launch', (browser = {}, launchOptions) => {
// const args = Array.isArray(launchOptions) ? launchOptions : launchOptions.args;
// port = ensureRdpPort(args);
// if (browser.name === 'chrome') {
// launchOptions.args.push('--disable-dev-shm-usage');
// return launchOptions;
// }
// return launchOptions;
// });
// simple memory key-value store
const store: { [key: string]: any } = {};
export default defineConfig({
e2e: {
specPattern: '**/*.test.{js,jsx,ts,tsx}',
baseUrl: 'http://localhost:8000',
fixturesFolder: './fixtures',
downloadsFolder: './downloads',
supportFile: './support/index.ts',
defaultCommandTimeout: 20000,
screenshotsFolder: './screenshots',
videosFolder: './videos',
video: true,
trashAssetsBeforeRuns: false,
chromeWebSecurity: false,
experimentalRunAllSpecs: true,
setupNodeEvents(on, config) {
on('before:browser:launch', _browser => {
// const args = Array.isArray(launchOptions) ? launchOptions : launchOptions.args;
// port = ensureRdpPort(args);
addMatchImageSnapshotPlugin(on, config);
});
on('task', {
metadataStartProvider: async provider => {
switch (provider) {
case 'dropbox':
await mocked.dropbox.start();
break;
case 'google':
await mocked.google.start();
break;
default:
throw new Error('not a valid case');
}
return null;
},
metadataStopProvider: provider => {
switch (provider) {
case 'dropbox':
mocked.dropbox.stop();
break;
case 'google':
mocked.google.stop();
break;
default:
throw new Error('not a valid case');
}
return null;
},
metadataSetFileContent: async ({ provider, file, content, aesKey }) => {
const encrypted = await metadataUtils.encrypt(content, aesKey);
switch (provider) {
case 'dropbox':
mocked.dropbox.setFile(file, encrypted);
break;
case 'google':
mocked.google.setFile(file, encrypted);
break;
default:
throw new Error('not a valid case');
}
return null;
},
metadataSetNextResponse: ({ provider, status, body }) => {
switch (provider) {
case 'dropbox':
mocked.dropbox.nextResponse.push({ status, body });
break;
case 'google':
mocked.google.nextResponse.push({ status, body });
break;
default:
throw new Error('not a valid case');
}
return null;
},
metadataGetRequests: ({ provider }) => {
switch (provider) {
case 'dropbox':
return mocked.dropbox.requests;
case 'google':
return mocked.google.requests;
default:
throw new Error('not a valid case');
}
},
startMockedBridge: async har => {
await mocked.bridge.start(har);
return null;
},
stopMockedBridge: async () => {
await mocked.bridge.stop();
return null;
},
stealBridgeSession: async () => {
const bridge = new BridgeTransport({ messages, id: 'foo-bar' });
await bridge.init();
const enumerateRes = await bridge.enumerate();
if (!enumerateRes.success) return null;
await bridge.acquire({
input: { path: enumerateRes.payload[0].path, previous: null },
});
return null;
},
resetCRI: async () => {
if (client) {
await client.close();
client = null;
}
return Promise.resolve(true);
},
readDir: dir => fs.readdirSync(dir, { encoding: 'utf-8' }),
readFile: path2 => fs.readFileSync(path2, { encoding: 'utf-8' }),
rmDir: (opts: {
recursive: fs.RmDirOptions['recursive'];
dir: string;
force?: boolean;
}) => {
const { dir, recursive, force } = opts;
// just a security check so that we do accidentally wipe something we don't want
const restrictedPath = path.join(__dirname, '..', config.downloadsFolder);
if (!dir.startsWith(restrictedPath) && !force) {
console.warn('trying to rmDir ', dir);
throw new Error(`'it is not allowed to rm outside ${restrictedPath}`);
}
if (fs.existsSync(dir)) {
fs.rmdirSync(dir, { recursive });
}
return null;
},
csvToJson(data) {
const lines = data.split('\n');
const result = [];
const headers = lines[0].split(',');
for (let i = 1; i < lines.length; i++) {
const obj: Record<string, string> = {};
const currentline = lines[i].split(',');
for (let j = 0; j < headers.length; j++) {
obj[headers[j]] = currentline[j];
}
result.push(obj);
}
return result;
},
async startBlockbookMock({ endpointsFile }) {
const { fixtures } = await import(`./fixtures/${endpointsFile}.ts`);
blockbook = await BackendWebsocketServerMock.create('blockbook');
blockbook.setFixtures(fixtures);
return blockbook.options.port;
},
stopBlockbookMock() {
if (blockbook) {
blockbook.stop();
}
return null;
},
set({ key, value }: { key: string; value: any }) {
store[key] = value;
return null;
},
get({ key }: { key: string }): any {
return store[key];
},
trezorUserEnvDisconnect() {
return TrezorUserEnvLink.disconnect();
},
trezorUserEnvConnect() {
return TrezorUserEnvLink.connect();
},
...(() =>
Object.getOwnPropertyNames(Object.getPrototypeOf(TrezorUserEnvLink)).reduce(
(acc, key) => {
// @ts-expect-error
if (typeof TrezorUserEnvLink[key] === 'function') {
return {
...acc,
// @ts-expect-error
[key]: TrezorUserEnvLink[key].bind(TrezorUserEnvLink),
};
}
return acc;
},
{},
))(),
async setupEmu(opts: Parameters<typeof TrezorUserEnvLink.setupEmu>[0]) {
await TrezorUserEnvLink.setupEmu(opts);
return TrezorUserEnvLink.startBridge();
},
});
},
},
});

View File

@@ -1,137 +0,0 @@
const ETH_ACC = {
page: 1,
totalPages: 1,
itemsOnPage: 25,
address: '0xcb6139253d4fa49712C08BF0Cb4F6ea6c2007bF5',
balance: '1234000000000000000000',
unconfirmedBalance: '0',
unconfirmedTxs: 0,
txs: 1,
nonTokenTxs: 1,
internalTxs: 0,
transactions: [
{
txid: '0xec38a68a61d7aebe95c0fdf122cef651a7084301e65ebc94d8ae40498bc84958',
vin: [
{
n: 0,
addresses: ['0x7de62F23453E9230cC038390901A9A0130105A3c'],
isAddress: true,
isOwn: true,
},
],
vout: [
{
value: '100000000000000000',
n: 0,
addresses: ['0xAFA848357154a6a624686b348303EF9a13F63264'],
isAddress: true,
},
],
blockHash: '0x10d4e0b9db8cf40055154760238f7470ae3f3ccc6b8c6139b454464a2c768e54',
blockHeight: 1469357,
confirmations: 152124,
blockTime: 1714736076,
value: '100000000000000000',
fees: '347242000000000',
ethereumSpecific: {
status: 1,
nonce: 9,
gasLimit: 416102,
gasUsed: 173621,
gasPrice: '2000000000',
data: '0x3a29dbae0000000000000000000000000000000000000000000000000000000000000001',
parsedData: {
methodId: '0x3a29dbae',
name: 'Stake',
function: 'stake(uint64)',
params: [
{
type: 'uint64',
values: ['1'],
},
],
},
internalTransfers: [
{
type: 0,
from: '0x02a9d3637126923De9369557CD9673aae46666Fd',
to: '0x66cb3AeD024740164EBcF04e292dB09b5B63A2e1',
value: '55324575000000000',
},
{
type: 0,
from: '0xAFA848357154a6a624686b348303EF9a13F63264',
to: '0x66cb3AeD024740164EBcF04e292dB09b5B63A2e1',
value: '100000000000000000',
},
],
},
},
],
stakingPools: [
{
contract: '0x624087DD1904ab122A32878Ce9e933C7071F53B9',
name: 'Everstake',
pendingBalance: '1000000000000000000000',
pendingDepositedBalance: '2000000000000000000000',
depositedBalance: '3000000000000000000000',
withdrawTotalAmount: '4000000000000000000000',
claimableAmount: '5000000000000000000000',
restakedReward: '1234000000000000000000',
autocompoundBalance: '7000000000000000000000',
},
],
nonce: '1',
tokens: [],
addressAliases: {},
};
const isFirstAccount = (descriptor: string) => descriptor === ETH_ACC.address;
export const fixtures = [
{
method: 'getInfo',
default: true,
response: {
id: '0',
data: {
name: 'Ethereum Archive',
shortcut: 'ETH',
decimals: 18,
version: '0.4.0',
bestHeight: 19960825,
bestHash: '0x8339e411cd2f62b9493e36c444f7bd11ec716ceaa94f491e9726233d22abc024',
block0Hash: '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3',
network: 'ETH',
testnet: false,
backend: {
version: 'erigon/2.59.3/linux-amd64/go1.21.6',
consensus_version: 'Prysm/v5.0.2 (linux amd64)',
},
},
},
},
{
method: 'getAccountInfo',
default: true,
response: ({ params }: any) => {
if (isFirstAccount(params.descriptor)) {
return { data: ETH_ACC };
}
},
},
{
method: 'estimateFee',
default: true,
response: {
data: [
{
feePerTx: '25293986739000',
feePerUnit: '1204475559',
feeLimit: '21000',
},
],
},
},
];

File diff suppressed because one or more lines are too long

View File

@@ -1,64 +0,0 @@
{
"platforms": {
"bitcoin-cash": {
"id": "bitcoin-cash",
"name": "Simple Ledger Protocol (Bitcoin Cash)"
},
"ethereum": {
"id": "ethereum",
"name": "Ethereum"
}
},
"coins": {
"bitcoin-cash": {
"symbol": "bch",
"name": "Bitcoin Cash",
"coingeckoId": "bitcoin-cash",
"services": {
"buy": true,
"sell": true,
"exchange": true
}
},
"bitcoin": {
"symbol": "btc",
"name": "Bitcoin",
"coingeckoId": "bitcoin",
"services": {
"buy": true,
"sell": true,
"exchange": true
}
},
"ethereum": {
"symbol": "eth",
"name": "Ethereum",
"coingeckoId": "ethereum",
"services": {
"buy": true,
"sell": true,
"exchange": true
}
},
"litecoin": {
"symbol": "ltc",
"name": "Litecoin",
"coingeckoId": "litecoin",
"services": {
"buy": true,
"sell": true,
"exchange": true
}
},
"ripple": {
"symbol": "xrp",
"name": "XRP",
"coingeckoId": "ripple",
"services": {
"buy": true,
"sell": true,
"exchange": true
}
}
}
}

View File

@@ -1,46 +0,0 @@
{
"country": "AT",
"suggestedFiatCurrency": "EUR",
"providers": [
{
"name": "btcdirect-sandbox",
"companyName": "BTC Direct",
"logo": "btcdirect-sandbox.png",
"isActive": true,
"tradedCoins": ["bitcoin-cash", "bitcoin", "ethereum", "litecoin", "ripple"],
"tradedFiatCurrencies": ["EUR"],
"supportedCountries": ["AT", "BE", "BG", "SL"],
"paymentMethods": [
"bancontact",
"bankTransfer",
"creditCard",
"giropay",
"iDeal",
"sofort"
],
"statusUrl": "https://my-sandbox.btcdirect.eu/en-gb/user/transactions?client=trezor",
"supportUrl": "https://support.btcdirect.eu/hc/en-gb?client=trezor"
},
{
"name": "banxa-sandbox",
"companyName": "Banxa",
"logo": "banxa-sandbox.png",
"isActive": true,
"tradedCoins": ["bitcoin-cash", "binancecoin", "bitcoin-sv", "bitcoin", "ripple"],
"tradedFiatCurrencies": ["AUD", "CAD", "SEK", "USD"],
"supportedCountries": ["DZ", "AS", "AD", "AX"],
"paymentMethods": [
"poli",
"auspost",
"bpay",
"creditCard",
"sofort",
"iDeal",
"dcinterac",
"bankTransfer"
],
"statusUrl": "https://banxa.com/support/",
"supportUrl": "https://banxa.com/support/"
}
]
}

View File

@@ -1,74 +0,0 @@
[
{
"fiatStringAmount": "500.00",
"fiatCurrency": "EUR",
"receiveCurrency": "bitcoin",
"receiveStringAmount": "0.02073954",
"rate": 24108.538569322172,
"quoteId": "mockedQuote3",
"paymentId": "mockedPaymentId3",
"exchange": "banxa",
"minFiat": 50,
"maxFiat": 30000,
"minCrypto": 0.00217306,
"maxCrypto": 1.30383763,
"paymentMethod": "bankTransfer",
"country": "AT",
"wantCrypto": false,
"partnerData2": "6053"
},
{
"fiatStringAmount": "500",
"fiatCurrency": "EUR",
"receiveCurrency": "bitcoin",
"receiveStringAmount": "0.02121718",
"rate": 23565.80846276461,
"quoteId": "mockedQuote1",
"paymentId": "mockedPaymentId1",
"exchange": "btcdirect",
"validUntil": "2022-08-03T10:48:33Z",
"minFiat": 30,
"maxFiat": 1000,
"minCrypto": 0.00130384,
"maxCrypto": 0.04346125,
"paymentMethod": "bankTransfer",
"country": "AT",
"wantCrypto": false
},
{
"fiatStringAmount": "500",
"fiatCurrency": "EUR",
"receiveCurrency": "bitcoin",
"receiveStringAmount": "0.0209449",
"rate": 23872.15980978663,
"quoteId": "mockedQuote2",
"paymentId": "mockedPaymentId2",
"exchange": "btcdirect",
"validUntil": "2022-08-03T10:48:33Z",
"minFiat": 30,
"maxFiat": 1000,
"minCrypto": 0.00130384,
"maxCrypto": 0.04346125,
"paymentMethod": "giropay",
"country": "AT",
"wantCrypto": false
},
{
"fiatStringAmount": "500.00",
"fiatCurrency": "EUR",
"receiveCurrency": "bitcoin",
"receiveStringAmount": "0.02073954",
"rate": 24108.538569322172,
"quoteId": "mockedQuote4",
"paymentId": "mockedPaymentId4",
"exchange": "banxa",
"minFiat": 50,
"maxFiat": 30000,
"minCrypto": 0.00217306,
"maxCrypto": 1.30383763,
"paymentMethod": "iDeal",
"country": "AT",
"wantCrypto": false,
"partnerData2": "6024"
}
]

View File

@@ -1,29 +0,0 @@
{
"trade": {
"fiatStringAmount": "500",
"fiatCurrency": "EUR",
"receiveCurrency": "bitcoin",
"receiveStringAmount": "0.02066953",
"rate": 24190.19687433628,
"orderId": "6476d7c4-a873-400f-8276-5a47a43cd392",
"paymentId": "4e9c3760220d839e8e30537d9ed5d172",
"originalPaymentId": "mockedPaymentId3",
"status": "APPROVAL_PENDING",
"exchange": "banxa",
"validUntil": "2022-07-31T16:42:22Z",
"minFiat": 30,
"maxFiat": 1001,
"minCrypto": 0.00128966,
"maxCrypto": 0.04303155,
"paymentMethod": "bankTransfer",
"country": "AT",
"wantCrypto": false
},
"tradeForm": {
"form": {
"formMethod": "GET",
"formAction": "https://simulated-partner.com",
"fields": {}
}
}
}

View File

@@ -1,3 +0,0 @@
{
"status": "SUCCESS"
}

View File

@@ -1,22 +0,0 @@
[
{
"ticker": "REGTEST",
"name": "Regtest",
"category": "Popular currencies"
},
{
"ticker": "ETH",
"name": "Ethereum",
"category": "Popular currencies"
},
{
"ticker": "ADA",
"name": "Cardano",
"category": "Popular currencies"
},
{
"ticker": "LTC",
"name": "Litecoin",
"category": "Popular currencies"
}
]

View File

@@ -1,50 +0,0 @@
[
{
"name": "changenowfr",
"companyName": "ChangeNOW",
"logo": "changenowfr-icon.jpg",
"isActive": true,
"isFixedRate": true,
"isDex": false,
"buyTickers": ["REGTEST", "LTC", "ETH", "ADA"],
"sellTickers": ["REGTEST", "LTC", "ETH", "ADA"],
"addressFormats": { "BCH": "legacy", "LTC": "type3" },
"statusUrl": "https://changenow.io/exchange/txs/{{orderId}}",
"kycUrl": "https://changenow.io/faq#kyc",
"supportUrl": "https://support.changenow.io",
"kycPolicy": "KYC requested in exceptional cases. KYC not required for refunds. 🤝",
"isRefundRequired": false
},
{
"name": "changelly",
"companyName": "Changelly",
"logo": "changelly-icon.jpg",
"isActive": true,
"isFixedRate": false,
"isDex": false,
"buyTickers": ["REGTEST", "LTC", "ETH", "ADA"],
"sellTickers": ["REGTEST", "LTC", "ETH", "ADA"],
"addressFormats": { "BCH": "cashaddr", "LTC": "type3" },
"statusUrl": "https://support.changelly.com",
"kycUrl": "https://changelly.com/aml-kyc",
"supportUrl": "https://support.changelly.com",
"kycPolicy": "KYC requested in exceptional cases. KYC required for refunds. 👈",
"isRefundRequired": false
},
{
"name": "changehero",
"companyName": "ChangeHero",
"logo": "changehero-icon.png",
"isActive": true,
"isFixedRate": false,
"isDex": false,
"buyTickers": ["REGTEST", "LTC", "ETH", "ADA"],
"sellTickers": ["REGTEST", "LTC", "ETH", "ADA"],
"addressFormats": { "BCH": "legacy", "LTC": "type3" },
"statusUrl": "https://changehero.io/transaction/{{orderId}}",
"kycUrl": "https://changehero.io/aml-kyc",
"supportUrl": "https://changehero.io/support",
"kycPolicy": "KYC requested in exceptional cases. KYC required for refunds. 👈",
"isRefundRequired": false
}
]

View File

@@ -1,36 +0,0 @@
[
{
"send": "REGTEST",
"sendStringAmount": "0.005",
"receive": "ETH",
"receiveStringAmount": "0.053845",
"rate": 12.219697768043531,
"min": 0.0018,
"max": "NONE",
"fee": "UNKNOWN",
"exchange": "changehero"
},
{
"send": "REGTEST",
"sendStringAmount": "0.005",
"receive": "ETH",
"receiveStringAmount": "0.062824",
"rate": 12.211581272726496,
"min": 0.00017350799999999998,
"max": 1.71004235,
"fee": "INCLUDED",
"rateIdentificator": "sI4vrh5vjTzUJWzvIISLAevvDdCfeZCb",
"exchange": "changenow"
},
{
"send": "REGTEST",
"sendStringAmount": "0.005",
"receive": "ETH",
"receiveStringAmount": "0.063230",
"rate": 11.802143287485098,
"min": 0.001247436,
"max": "NONE",
"fee": "UNKNOWN",
"exchange": "changelly"
}
]

View File

@@ -1,17 +0,0 @@
{
"send": "REGTEST",
"sendStringAmount": "0.005",
"sendAddress": "2N4dH9yn4eYnnjHTYpN9xDmuMRS2k1AHWd8",
"receive": "ETH",
"receiveStringAmount": "0.053845",
"receiveAddress": "0x8185b57ac7ee339245dd2c06Bdd056Aec2844d4D",
"rate": 12.219697768043531,
"min": 0.0018,
"max": 5,
"fee": "INCLUDED",
"orderId": "rbflttdfuhnigd9ivd",
"statusUrl": "https://changehero.io/transaction/rbflttdfuhnigd9ivd",
"status": "CONFIRM",
"rateIdentificator": "e4a2dc4b-6e8e-41c5-b06d-0a5fa5f0cbbe",
"exchange": "changehero"
}

View File

@@ -1,3 +0,0 @@
{
"status": "SUCCESS"
}

View File

@@ -1,225 +0,0 @@
const PREV_TX = {
txid: '3796497830e70bbf60fbdd8f9570ed53851df6e3d7a34fb1eff2119caf03616d',
version: 1,
vin: [
{
txid: 'adf4a7fc64379932db9690c6f28a731e3322df9cc1311d09a742f407cf3396c6',
sequence: 4294967295,
n: 0,
addresses: ['DN1ZMYxicfqLqRWcgftsS2iTAUhmLzcDKK'],
isAddress: true,
value: '1000000000',
hex: '47304402207cecdcab4b1c631bbed8da9776c91d5fff3e1afde9e91bfa44e7decffbd8331c022036fec7707d5371a2346ebb52d0ce2805ce6409439b8d6338b05b2c1df485b10f012102960563167bea073f7979cfe5525ca3a24655d3560894f577ab452e19216eda87',
},
],
vout: [
{
value: '11556856856844445555',
n: 0,
hex: '76a9148ae3fead7569f965e334788cc40176b3ecee571588ac',
addresses: ['DHoUv6AUXrxadt5GhqK2TLN6Lw4bgKZnjG'],
isAddress: true,
},
],
blockHash: 'deadfea6fa7eaee8568f5ef04c4ea0b1b2c47e08299ec6fd4c45fb4b89792088',
blockHeight: 4484551,
confirmations: 79,
blockTime: 1669212266,
value: '11556856856844445555',
valueIn: '1000000000',
fees: '19200',
hex: '0100000001c69633cf07f442a7091d31c19cdf22331e738af2c69096db32993764fca7f4ad000000006a47304402207cecdcab4b1c631bbed8da9776c91d5fff3e1afde9e91bfa44e7decffbd8331c022036fec7707d5371a2346ebb52d0ce2805ce6409439b8d6338b05b2c1df485b10f012102960563167bea073f7979cfe5525ca3a24655d3560894f577ab452e19216eda87ffffffff01007f9a3b000000001976a9148ae3fead7569f965e334788cc40176b3ecee571588ac00000000',
};
const DOGE_ACCOUNT = {
data: {
page: 1,
totalPages: 1,
itemsOnPage: 25,
address:
'dgub8rE9bgXY9DuS9fvhBMshxu6auWGdAcD9P6weqZrqnLx13edD963SB3kjHBTWYWBWErhpLW9SkopNwx7zLdvDt1nySVb8qj8zR95iH4578JN',
balance: '11556856856844445555',
totalReceived: '11556856856844445555',
totalSent: '0',
unconfirmedBalance: '0',
unconfirmedTxs: 0,
txs: 1,
transactions: [
{
txid: '3796497830e70bbf60fbdd8f9570ed53851df6e3d7a34fb1eff2119caf03616d',
version: 1,
vin: [
{
txid: 'adf4a7fc64379932db9690c6f28a731e3322df9cc1311d09a742f407cf3396c6',
sequence: 4294967295,
n: 0,
addresses: ['DN1ZMYxicfqLqRWcgftsS2iTAUhmLzcDKK'],
isAddress: true,
value: '1000000000',
hex: '47304402207cecdcab4b1c631bbed8da9776c91d5fff3e1afde9e91bfa44e7decffbd8331c022036fec7707d5371a2346ebb52d0ce2805ce6409439b8d6338b05b2c1df485b10f012102960563167bea073f7979cfe5525ca3a24655d3560894f577ab452e19216eda87',
},
],
vout: [
{
value: '11556856856844445555',
n: 0,
hex: '76a9148ae3fead7569f965e334788cc40176b3ecee571588ac',
addresses: ['DHoUv6AUXrxadt5GhqK2TLN6Lw4bgKZnjG'],
isAddress: true,
isOwn: true,
},
],
blockHash: 'deadfea6fa7eaee8568f5ef04c4ea0b1b2c47e08299ec6fd4c45fb4b89792088',
blockHeight: 4484551,
confirmations: 16,
blockTime: 1669212266,
value: '11556856856844445555',
valueIn: '1000000000',
fees: '19200',
hex: '0100000001c69633cf07f442a7091d31c19cdf22331e738af2c69096db32993764fca7f4ad000000006a47304402207cecdcab4b1c631bbed8da9776c91d5fff3e1afde9e91bfa44e7decffbd8331c022036fec7707d5371a2346ebb52d0ce2805ce6409439b8d6338b05b2c1df485b10f012102960563167bea073f7979cfe5525ca3a24655d3560894f577ab452e19216eda87ffffffff01007f9a3b000000001976a9148ae3fead7569f965e334788cc40176b3ecee571588ac00000000',
},
],
usedTokens: 1,
tokens: [
{
type: 'XPUBAddress',
name: 'DHoUv6AUXrxadt5GhqK2TLN6Lw4bgKZnjG',
path: "m/44'/3'/0'/0/0",
transfers: 1,
decimals: 8,
balance: '11556856856844445555',
totalReceived: '11556856856844445555',
totalSent: '0',
},
{
type: 'XPUBAddress',
name: 'DBNkNukxRcH6QTPSp69KhVHQWkUpVLotWs',
path: "m/44'/3'/0'/0/1",
transfers: 0,
decimals: 8,
},
{
type: 'XPUBAddress',
name: 'DCsP6RCfFHvTKFRocJEoW1CjYEPMk9pk3i',
path: "m/44'/3'/0'/1/0",
transfers: 0,
decimals: 8,
},
{
type: 'XPUBAddress',
name: 'DMRZ3D9ppogDQoY7AUCLy5EaRTTEv6xJ5f',
path: "m/44'/3'/0'/1/1",
transfers: 0,
decimals: 8,
},
],
},
};
const isFirstAccount = (descriptor: string) => descriptor === DOGE_ACCOUNT.data.address;
export const fixtures = [
{
method: 'getInfo',
default: true,
response: {
data: {
name: 'DogecoinMock',
shortcut: 'DOGE',
decimals: 8,
version: '0.3.6',
bestHeight: 4484566,
bestHash: '38fbb073e7eb4bfac9ed485dd0b3212247fb2a3d8b509dc9738c01a86ede33b3',
block0Hash: '1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691',
network: 'DOGE',
testnet: false,
backend: { version: '1140600', subversion: '/Shibetoshi:1.14.6/' },
},
},
},
{
method: 'getAccountInfo',
default: true,
response: ({ params }: any) => {
if (isFirstAccount(params.descriptor)) {
return DOGE_ACCOUNT;
}
},
},
{
method: 'getAccountUtxo',
default: true,
response: {
data: [
{
txid: PREV_TX.txid,
vout: 0,
value: '11556856856844445555',
height: 4484551,
confirmations: 16,
address: 'DHoUv6AUXrxadt5GhqK2TLN6Lw4bgKZnjG',
path: "m/44'/3'/0'/0/0",
},
],
},
},
{
method: 'getBalanceHistory',
default: true,
response: {
data: [
{
time: 1669161600,
txs: 1,
received: '11556856856844445555',
sent: '0',
sentToSelf: '0',
rates: { usd: 0.078881 },
},
],
},
},
{
method: 'estimateFee',
default: true,
response: {
data: [
{ feePerUnit: '-100000000' },
{ feePerUnit: '6418769' },
{ feePerUnit: '1003782' },
{ feePerUnit: '1003782' },
{ feePerUnit: '1003782' },
{ feePerUnit: '1003782' },
{ feePerUnit: '1003782' },
{ feePerUnit: '1003782' },
{ feePerUnit: '1003782' },
{ feePerUnit: '1003782' },
],
},
},
{
method: 'getFiatRatesForTimestamps',
default: true,
response: {
data: {
tickers: [
{
ts: 1668608419,
rates: {
usd: 0.085339,
},
},
],
},
},
},
{
method: 'getTransaction',
default: true,
response: ({ params }: any) => {
if (params?.txid === PREV_TX.txid) {
return { data: PREV_TX };
}
},
},
];

View File

@@ -1,159 +0,0 @@
const PREV_TX = {
txid: 'efe11e0d8d562e73b7795c2a3b7e44c6b6390f2c42c3ae90bb1005009c27a3f3',
version: 2,
vin: [
{
txid: '5961c1187b435a42e144a1c609253bca80ac0c55caf576831b6459c77fb690f1',
sequence: 4294967295,
n: 0,
addresses: ['ltc1gp6wfe96yxq855dhjjg0eg24yl8y6se62hgcadmh6jdyxfdlljy6slgmvea'],
isAddress: false,
value: '458340830086',
},
],
vout: [
{
value: '458330830086',
n: 0,
spent: true,
hex: '582066723521c495f90a5fbe3686c617c294d69ac71ed9e57b65032f83e45871fd83',
addresses: ['ltc1gveer2gwyjhus5ha7x6rvv97zjntf43c7m8jhkegr97p7gkr3lkpsj08e2q'],
isAddress: false,
},
{
value: '9997490',
n: 1,
hex: '0014f4de962f4bb82d0057974201202acd78d56db7f2',
addresses: ['ltc1q7n0fvt6thqksq4uhggqjq2kd0r2kmdlje4dvjg'],
isAddress: true,
},
],
blockHash: '98cdd21f13d92ccd23478681d6698aea20a7132d7ca9bec1204e0d25e9355eba',
blockHeight: 2373435,
confirmations: 29,
blockTime: 1669107971,
value: '458340827576',
valueIn: '458340830086',
fees: '2510',
hex: '02000000000801f190b67fc759641b8376f5ca550cac80ca3b2509c6a144e1425a437b18c161590000000000ffffffff020675a5b66a00000022582066723521c495f90a5fbe3686c617c294d69ac71ed9e57b65032f83e45871fd83b28c980000000000160014f4de962f4bb82d0057974201202acd78d56db7f20000000000',
};
const LTC_ACCOUNT = {
page: 1,
totalPages: 1,
itemsOnPage: 25,
address:
'zpub6rDfq4mDgVKjjFz9cqWizJQvJyHKss4NhBzuiSaoJ5k23cCFyU2WQFrZEJPorsZt4soMDJtZBHL3ZyH8HfqkYSo7qzebbHL4QhpSTXUNpAq',
balance: '9997490',
totalReceived: '9997490',
totalSent: '0',
unconfirmedBalance: '0',
unconfirmedTxs: 0,
txs: 1,
transactions: [PREV_TX],
usedTokens: 1,
tokens: [
{
type: 'XPUBAddress',
name: 'ltc1q7n0fvt6thqksq4uhggqjq2kd0r2kmdlje4dvjg',
path: "m/84'/2'/0'/0/0",
transfers: 1,
decimals: 8,
balance: '9997490',
totalReceived: '9997490',
totalSent: '0',
},
{
type: 'XPUBAddress',
name: 'ltc1qjwamf85d5ua429l256pugmkp89rhkezndh8t3t',
path: "m/84'/2'/0'/1/0",
transfers: 0,
decimals: 8,
},
{
type: 'XPUBAddress',
name: 'ltc1q6fwte9tar4dftxuarz8lwyfnwdmmt9azxjatpl',
path: "m/84'/2'/0'/1/1",
transfers: 0,
decimals: 8,
},
],
};
const isFirstAccount = (descriptor: string) => descriptor === LTC_ACCOUNT.address;
export const fixtures = [
{
method: 'getInfo',
default: true,
response: {
data: {
name: 'LitecoinMock',
shortcut: 'LTC',
decimals: 8,
version: '0.3.6',
bestHeight: 2373436,
bestHash: '2c1bc2b99f8a4447a57dfc7b694b9c82bff1b3af7cce8ff151df01238dc07c8b',
block0Hash: '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2',
network: 'LTC',
testnet: false,
backend: { version: '210201', subversion: '/LitecoinCore:0.21.2.1/' },
},
},
},
{
method: 'getAccountInfo',
default: true,
response: ({ params }: any) => {
if (isFirstAccount(params.descriptor)) {
return { data: LTC_ACCOUNT };
}
},
},
{
method: 'getAccountUtxo',
default: true,
response: {
data: [
{
txid: PREV_TX.txid,
vout: 1,
value: '9997490',
height: 2373435,
confirmations: 2,
address: 'ltc1q7n0fvt6thqksq4uhggqjq2kd0r2kmdlje4dvjg',
path: "m/84'/2'/0'/0/0",
},
],
},
},
{
method: 'estimateFee',
default: true,
response: {
data: [
{ feePerUnit: '999' },
{ feePerUnit: '999' },
{ feePerUnit: '999' },
{ feePerUnit: '999' },
{ feePerUnit: '999' },
{ feePerUnit: '999' },
{ feePerUnit: '999' },
{ feePerUnit: '999' },
{ feePerUnit: '999' },
{ feePerUnit: '999' },
],
},
},
{
method: 'getTransaction',
default: true,
response: ({ params }: any) => {
if (params?.txid === PREV_TX.txid) {
return {
data: PREV_TX,
};
}
},
},
];

View File

@@ -1,37 +0,0 @@
import fs from 'fs';
import path from 'path';
const fixturesPath = './fixtures';
const filePath = path.join(__dirname, fixturesPath);
const fileName = process.argv[process.argv.length - 1];
const raw = fs.readFileSync(path.join(filePath, fileName), 'utf-8');
const json = JSON.parse(raw);
const requests = json.log.entries
.filter((entry: any) => entry.request.url.startsWith('http://127.0.0.1:21325'))
.map((entry: any) => ({
request: {
method: entry.request.method,
url: entry.request.url.replace('http://127.0.0.1:21325', ''),
postData: entry.request.postData,
},
response: {
status: entry.response.status,
statusText: entry.response.statusText,
headers: entry.response.headers.filter((header: any) =>
['access-control-allow-origin', 'content-length', 'date', 'content-type'].includes(
header.name.toLowerCase(),
),
),
content: entry.response.content,
},
}));
fs.writeFileSync(
path.join(filePath, fileName.replace('.har', '.js')),
`export default ${JSON.stringify(requests, null, 2)}`,
);
// todo remove original HAR file

View File

@@ -1,399 +0,0 @@
/*
Heavily inspired by Mattermost,
https://github.com/mattermost/mattermost-webapp/blob/master/e2e/run_tests.js good job guys.
*/
import child_process from 'child_process';
import cypress from 'cypress';
import fs from 'fs';
import path from 'path';
import yargs from 'yargs/yargs';
import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link';
import cypressConfig from './cypress.config';
const TEST_DIR = __dirname;
const { argv } = yargs(process.argv.slice(2)).options({
group: { type: 'string' },
});
const getGrepCommand = (word = '', args = '-rlIw', path2 = TEST_DIR) =>
// -r, recursive search on subdirectories
// -I, ignore binary
// -l, only names of files to stdout/return
// -w, expression is searched for as a word
[args, word, path2];
/**
* get value of labels, for example there is
* @retry=3 in ./tests/backup.test.ts
* grepForValue('@retry', './tests/backup.test.ts');
* will return 3
*/
const grepForValue = (word: string, path2: string) => {
const command = getGrepCommand(word, '-rIw', path2);
const res = child_process.spawnSync('grep', command, {
encoding: 'utf-8',
});
if (res.stderr) {
throw new Error(res.stderr);
}
return res.stdout.replace(`// ${word}=`, '');
};
const getTestFiles = async (): Promise<string[]> => {
const { group } = await argv;
let command;
if (group) {
// for arrays
// command = getGrepCommand(stage.split(',').join('\\|'))
command = getGrepCommand(group);
} else {
command = getGrepCommand();
}
const res = child_process.spawnSync('grep', command, {
encoding: 'utf-8',
});
if (res.error) {
throw new Error(res.error.message);
}
return res.stdout.split('\n').filter((f: string) => f.includes('.test.'));
};
interface CIEnvVars {
BROWSER: string;
CYPRESS_baseUrl: string;
TRACK_SUITE_URL: string;
ALLOW_RETRY: boolean;
CYPRESS_updateSnapshots: boolean;
CYPRESS_TEST_URLS: string;
CI_JOB_URL: string;
CI_COMMIT_BRANCH: string;
CI_JOB_ID: string;
CI_COMMIT_MESSAGE: string;
CI_COMMIT_SHA: string;
}
function parse(expected: 'string', str: any): string;
function parse(expected: 'number', str: any): number;
function parse(expected: 'boolean', str: any): boolean;
function parse(expected: any, str: any): any {
if (str && typeof str !== 'string') {
throw new Error(`Expected string, got ${str} (type ${typeof str})`);
}
if (expected === 'string') {
return str;
}
if (expected === 'number') {
const asNum = parseInt(str, 10);
if (Number.isNaN(asNum)) {
throw new Error(`Expected number, got ${str} (type ${typeof str})`);
}
return asNum;
}
if (expected === 'boolean') {
if (str === 'true') return true;
if (str === 'false') return false;
throw new Error(`Expected boolean, got ${str} (type ${typeof str})`);
}
}
const collectCIEnvVars = (): CIEnvVars => {
const BROWSER = parse('string', process.env.BROWSER) || 'chrome';
const CYPRESS_baseUrl = parse('string', process.env.CYPRESS_baseUrl);
const TRACK_SUITE_URL = parse('string', process.env.TRACK_SUITE_URL);
const ALLOW_RETRY = parse('boolean', process.env.ALLOW_RETRY);
const CYPRESS_updateSnapshots = parse('boolean', process.env.CYPRESS_updateSnapshots);
const CYPRESS_TEST_URLS = parse('string', process.env.CYPRESS_TEST_URLS);
const CI_JOB_URL = parse('string', process.env.CI_JOB_URL);
const CI_COMMIT_BRANCH = parse('string', process.env.CI_COMMIT_BRANCH);
const CI_JOB_ID = parse('string', process.env.CI_JOB_ID);
const CI_COMMIT_MESSAGE = parse('string', process.env.CI_COMMIT_MESSAGE);
const CI_COMMIT_SHA = parse('string', process.env.CI_COMMIT_SHA);
return {
BROWSER,
CYPRESS_baseUrl,
TRACK_SUITE_URL,
ALLOW_RETRY,
CYPRESS_updateSnapshots,
CYPRESS_TEST_URLS,
CI_JOB_URL,
CI_COMMIT_BRANCH,
CI_JOB_ID,
CI_COMMIT_MESSAGE,
CI_COMMIT_SHA,
};
};
const runTests = async () => {
await TrezorUserEnvLink.connect();
const {
CI_JOB_URL,
CI_COMMIT_BRANCH,
CI_JOB_ID,
CI_COMMIT_MESSAGE,
CI_COMMIT_SHA,
CYPRESS_baseUrl,
TRACK_SUITE_URL,
ALLOW_RETRY,
CYPRESS_updateSnapshots,
CYPRESS_TEST_URLS,
BROWSER,
} = collectCIEnvVars();
console.log('CI_JOB_URL', CI_JOB_URL);
console.log('CI_COMMIT_BRANCH', CI_COMMIT_BRANCH);
console.log('CI_JOB_ID', CI_JOB_ID);
console.log('CI_COMMIT_MESSAGE', CI_COMMIT_MESSAGE);
console.log('CI_COMMIT_SHA', CI_COMMIT_SHA);
console.log('CYPRESS_baseUrl', CYPRESS_baseUrl);
console.log('TRACK_SUITE_URL', TRACK_SUITE_URL);
console.log('ALLOW_RETRY', ALLOW_RETRY);
console.log('CYPRESS_updateSnapshots', CYPRESS_updateSnapshots);
console.log('CYPRESS_TEST_URLS', CYPRESS_TEST_URLS);
console.log('BROWSER', BROWSER);
if (!CYPRESS_TEST_URLS) {
throw new Error('CYPRESS_TEST_URLS is not set');
}
const { group } = await argv;
if (!TRACK_SUITE_URL || CYPRESS_updateSnapshots) {
console.log(
'[run_tests.js] TRACK_SUITE_URL env not specified or CYPRESS_updateSnapshots is set. No logs will be uploaded',
);
}
const finalTestFiles = (await getTestFiles()).sort((a: string, b: string) =>
a.localeCompare(b),
);
if (!finalTestFiles.length) {
console.log('[run_tests.js] nothing to test!');
throw new Error('No test files found.');
}
console.log('[run_tests.js] test files after all filters:', finalTestFiles);
let failedTests = 0;
interface Log {
jobUrl?: string;
jobId?: string;
branch?: string;
commitMessage?: string;
commitSha?: string;
duration: number;
stage?: string;
records: { [key: string]: 'success' | 'failed' | 'retried' | 'skipped' };
tests: CypressCommandLine.TestResult[];
}
const log: Log = {
jobUrl: CI_JOB_URL,
jobId: CI_JOB_ID,
branch: CI_COMMIT_BRANCH,
commitMessage: CI_COMMIT_MESSAGE,
commitSha: CI_COMMIT_SHA,
duration: 0,
stage: group,
records: {},
tests: [],
};
for (let i = 0; i < finalTestFiles.length; i++) {
const testFile = finalTestFiles[i];
const retries = Number(grepForValue('@retry', testFile));
const allowedRuns = !Number.isNaN(retries) && Number(ALLOW_RETRY) ? retries + 1 : 1;
const spec = path.join(__dirname, testFile.substring(testFile.lastIndexOf('/tests')));
const testFileName = testFile
.substring(testFile.lastIndexOf('/tests/') + 7)
.replace('.test.ts', '');
console.log(`[run_tests.js] testing next file ${testFile}`);
console.log(`[run_tests.js] allowed to run ${allowedRuns} times`);
const config = {
e2e: {
...cypressConfig.e2e,
baseUrl: CYPRESS_baseUrl,
supportFile: `${__dirname}/support/index.ts`,
fixturesFolder: `${__dirname}/fixtures`,
screenshotsFolder: `${__dirname}/screenshots`,
videosFolder: `${__dirname}/videos`,
downloadsFolder: `${__dirname}/downloads`,
video: true,
chromeWebSecurity: false,
trashAssetsBeforeRuns: false,
defaultCommandTimeout: 15000,
env: {
TEST_URLS: CYPRESS_TEST_URLS.split(' '),
},
},
};
const userAgent = grepForValue('@user-agent', testFile);
if (userAgent) {
console.log('[run_tests.js] using custom user agent', userAgent);
Object.assign(config.e2e, { userAgent });
}
let testRunNumber = 0;
while (testRunNumber < allowedRuns) {
testRunNumber++;
console.log(`[run_tests.js] config.e2e.baseUrl: ${config.e2e.baseUrl}`);
try {
const runResult = await cypress.run({
browser: BROWSER,
// headless,
headed: true,
spec,
project: TEST_DIR,
config: config.e2e,
});
if ('status' in runResult && runResult.status === 'failed') {
// This block will only be entered if runResult is of type CypressFailedRunResult
throw new Error(runResult.message);
}
if (
'totalFailed' in runResult &&
'totalPending' in runResult &&
'totalDuration' in runResult
) {
// This block will only be entered if runResult is of type CypressRunResult
const { totalFailed, totalPending, totalDuration } = runResult;
const { tests } = runResult.runs[0];
console.log(`[run_tests.js] ${testFileName} duration: ${totalDuration}`);
log.duration += totalDuration;
if (totalFailed > 0) {
// record failed tests if it is the last run
if (testRunNumber === allowedRuns) {
failedTests += totalFailed;
log.records[testFileName] = 'failed';
log.tests.push(...tests);
console.log(
`[run_tests.js] test ${testFileName} finished failing after ${allowedRuns} run(s)`,
);
break;
}
// or continue
console.log(
`[run_tests.js] failed in run number ${testRunNumber} of ${allowedRuns}`,
);
continue;
}
log.tests.push(...tests);
if (totalPending > 0) {
// log either success or retried (success after retry)
log.records[testFileName] = 'skipped';
console.log(`[run_tests.js] test ${testFileName} finished as skipped`);
break;
}
// log either success or retried (success after retry)
log.records[testFileName] = testRunNumber === 1 ? 'success' : 'retried';
console.log(
`[run_tests.js] test ${testFileName} finished as successful after ${testRunNumber} run(s) (of ${allowedRuns})`,
);
break;
}
} catch (err) {
console.log('[run_tests.js] error');
console.log(err);
process.exit(1);
}
}
}
if (TRACK_SUITE_URL && !CYPRESS_updateSnapshots) {
console.log(`[run_tests.js] uploading logs to ${TRACK_SUITE_URL}.`);
const response = await fetch(`${TRACK_SUITE_URL}/api/test-records`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(log),
});
console.log(`[run_tests.js] response.status: ${response.status}`);
if (!response.ok) {
console.log('[run_tests.js] response.error', response);
}
}
console.log('CYPRESS_updateSnapshots', CYPRESS_updateSnapshots);
if (CYPRESS_updateSnapshots) {
const script = `
#!/bin/sh
diff_pre=$(git status --porcelain=v1 2>/dev/null | wc -l)
if [ $diff_pre -gt 0 ]
then
echo "You have unstaged changes."
exit 1
fi
mkdir tmp
cd tmp
wget ${CI_JOB_URL}/artifacts/download
unzip download
cp -rf packages/integration-tests/projects/suite-web/snapshots ../packages/integration-tests/projects/suite-web
cd ../
rm -rf ./tmp
git status
diff_after=$(git status --porcelain=v1 2>/dev/null | wc -l)
if [ $diff_after -eq 0 ]
then
echo "There are no new snapshots."
exit 0
fi
git add .
git commit -m "e2e${group ? `(${group}):` : ':'} update snapshots"
git log -n 2
echo "You may now push your changes."
`;
console.log('Generated script to update files locally');
console.log(script);
console.log(`
EXECUTE ^^ SCRIPT TO UPDATE SNAPSHOTS THAT CHANGED LOCALLY
*******************************************************************
curl ${CI_JOB_URL}/artifacts/raw/download-snapshots.sh | bash
*******************************************************************
`);
fs.appendFileSync('download-snapshots.sh', script);
} else {
console.log(
`[run_tests.js] Logs recorded ${TRACK_SUITE_URL}/#/${CI_COMMIT_BRANCH}/${CI_COMMIT_SHA}.`,
);
}
process.exit(failedTests);
};
runTests();

View File

@@ -1,41 +0,0 @@
// process of getting oauth token involves widow.open and waits for post message from it.
// Cypress can't touch other windows/tabs it so what we do here is that we replace implementation
// of window.open to invoke only postMessage with data that satisfy application flow
export const stubOpen = (win: Window) => {
// @ts-expect-error
win.Math.random = () => 0.4; // to make tests deterministic, this value ensures state YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
return () =>
win.postMessage({ search: '?code=chicken-cho-cha&state=YYYYYYYYYY', key: 'trezor-oauth' });
};
export const rerouteMetadataToMockProvider = (
uri: string,
options: Parameters<typeof fetch>[1],
) => {
let url;
try {
url = new URL(uri);
} catch {
// When requests like `./data/firmware/t3w1/releases.json` from connect they need to be
// provided with baseUrl so cypress can figure out where it is going.
const baseUrl = window.location.origin;
uri = new URL(uri, baseUrl).href;
return fetch(uri, options);
}
const dropboxOrigins = ['https://content.dropboxapi.com', 'https://api.dropboxapi.com'];
if (dropboxOrigins.some(o => uri.includes(o))) {
return fetch(url.href.replace(url.origin, 'http://localhost:30002'), options);
}
const googleOrigins = ['https://www.googleapis.com', 'https://oauth2.googleapis.com'];
if (googleOrigins.some(o => uri.includes(o))) {
return fetch(url.href.replace(url.origin, 'http://localhost:30001'), options);
}
return fetch(uri, options);
};

View File

@@ -1,208 +0,0 @@
/* eslint-disable @typescript-eslint/no-namespace */
import { SuiteAnalyticsEvent } from '@trezor/suite-analytics';
import { EventPayload, Requests } from './types';
import {
dashboardShouldLoad,
discoveryMightAppearAndShouldFinish,
discoveryShouldFinish,
onboardingShouldLoad,
} from './utils/assertions';
import { interceptInvityApi } from './utils/intercept-invity-api';
import { getConfirmActionOnDeviceModal, getTestElement, hoverTestElement } from './utils/selectors';
import {
addHiddenWallet,
changeViewOnlyState,
clearInput,
createAccountFromMyAccounts,
disableFirmwareHashCheck,
enableDebugMode,
enableRegtestAndGetCoins,
enterPinOnBlindMatrix,
findAnalyticsEventByType,
interceptDataTrezorIo,
passThroughAuthenticityCheck,
passThroughBackup,
passThroughBackupShamir,
passThroughInitMetadata,
passThroughInitialRun,
passThroughSetPin,
toggleDebugModeInSettings,
toggleDeviceMenu,
} from './utils/shortcuts';
import { resetDb } from './utils/test-env';
const command = require('cypress-image-snapshot/command');
const prefixedVisit = (route: string, options?: Partial<Cypress.VisitOptions>) => {
const baseUrl = Cypress.config('baseUrl');
const assetPrefix = Cypress.env('ASSET_PREFIX') || '';
const testUrl = Cypress.env('TEST_URLS')?.[0] || '';
cy.visit(baseUrl + testUrl + assetPrefix + route, options);
return cy.document().its('fonts.status').should('equal', 'loaded');
};
const safeReload = () => {
// waiting for:
// - device is released
// - writes to indexedDB are finished
cy.wait(2000);
return cy.reload();
};
beforeEach(() => {
console.log('Cypress.env', Cypress.env('USE_TREZOR_USER_ENV'));
const suiteName = (Cypress as any).mocha.getRunner().suite.ctx.currentTest.parent.title;
const testName = (Cypress as any).mocha.getRunner().suite.ctx.currentTest.title;
cy.task('trezorUserEnvConnect');
cy.task('logTestDetails', `New test case: ${suiteName} - ${testName}`);
cy.log('stop bridge before every test to make sure that there is no pending session');
cy.task('stopBridge');
cy.task('stopEmu');
cy.task('stopMockedBridge');
if (Cypress.env('USE_TREZOR_USER_ENV_BRIDGE')) {
cy.intercept('*', { hostname: '127.0.0.1' }, req => {
req.url = req.url.replace('21325', '21326');
});
}
cy.task('resetCRI');
// disable messaging system on develop
cy.intercept('https://data.trezor.io/config/develop/config.v1.jws', req => {
const mock =
'eyJhbGciOiJFUzI1NiJ9.ewogICAgInZlcnNpb24iOiAxLAogICAgInRpbWVzdGFtcCI6ICIyMDIyLTA0LTA0VDAwOjAwOjAwKzAwOjAwIiwKICAgICJzZXF1ZW5jZSI6IDEwMCwKICAgICJhY3Rpb25zIjogW10KfQo.6LBUsZIxdDGLxVuHQNvFmphVdRwxMpmEHhRC-vU4horpzWwIlvex8R7w48YInk231OxxovrHX8pVvCDWPaoWRA';
req.continue(res => {
res.send(mock);
});
});
// disable messaging system in codesign build
cy.intercept('https://data.trezor.io/config/stable/config.v1.jws', req => {
const mock =
'eyJhbGciOiJFUzI1NiJ9.ewogICAgInZlcnNpb24iOiAxLAogICAgInRpbWVzdGFtcCI6ICIyMDIyLTA0LTA0VDAwOjAwOjAwKzAwOjAwIiwKICAgICJzZXF1ZW5jZSI6IDEwMCwKICAgICJhY3Rpb25zIjogW10KfQo.rM_IWzbu3iRelYC9fB7YA3sHtCWXTJAKTgxJ5WszUj__BTEIvBbd5iBFSaDoNrY4CZejxNCbnzMTLnb5x6ZN2A';
req.continue(res => {
res.send(mock);
});
});
// cy.visit('/');
});
afterEach(() => {
cy.task('trezorUserEnvDisconnect');
cy.task('stopMockedBridge');
});
const step = (description: string, block: () => void) => {
cy.log(description);
block();
};
declare global {
namespace Cypress {
interface Chainable<Subject> {
step: typeof step;
getTestElement: typeof getTestElement;
hoverTestElement: typeof hoverTestElement;
prefixedVisit: typeof prefixedVisit;
getConfirmActionOnDeviceModal: typeof getConfirmActionOnDeviceModal;
resetDb: typeof resetDb;
// todo: better types, this is not 100% correct as this fn may get more args from
// cypress-image-snapshot lib
matchImageSnapshot: (typeof cy)['screenshot'];
onboardingShouldLoad: () => Chainable<Subject>;
dashboardShouldLoad: () => Chainable<Subject>;
discoveryShouldFinish: () => Chainable<Subject>;
discoveryMightAppearAndShouldFinish: () => Chainable<Subject>;
toggleDeviceMenu: () => Chainable<Subject>;
enableDebugMode: () => Chainable<Subject>;
disableFirmwareHashCheck: () => Chainable<Subject>;
toggleDebugModeInSettings: () => Chainable<Subject>;
text: () => Chainable<Subject>;
passThroughInitialRun: (
params?: Parameters<typeof passThroughInitialRun>[0],
) => Chainable<Subject>;
passThroughAuthenticityCheck: () => Chainable<Subject>;
passThroughBackup: () => Chainable<Subject>;
passThroughBackupShamir: (shares: number, threshold: number) => Chainable<Subject>;
passThroughSetPin: () => Chainable<Subject>;
passThroughInitMetadata: (provider: 'dropbox' | 'google') => Chainable<Subject>;
enableRegtestAndGetCoins: (params: {
payments: { address: string; amount: number }[];
}) => Chainable<Subject>;
skipOn: (nameOrFlag: string | boolean, cb?: () => void) => Cypress.Chainable<any>;
onlyOn: (nameOrFlag: string | boolean, cb?: () => void) => Cypress.Chainable<any>;
createAccountFromMyAccounts: (coin: string, label: string) => Chainable<Subject>;
interceptInvityApi: () => void;
interceptDataTrezorIo: (requests: Requests) => Cypress.Chainable<null>;
findAnalyticsEventByType: <T extends SuiteAnalyticsEvent>(
requests: Requests,
eventType: T['type'],
) => Cypress.Chainable<NonNullable<EventPayload<T>>>;
enterPinOnBlindMatrix: (entryPinNumber: string) => Cypress.Chainable<null>;
safeReload: typeof safeReload;
addHiddenWallet: (passphrase: string) => Chainable<Subject>;
changeViewOnlyState: (
walletIndex: number,
desiredState: 'enabled' | 'disabled',
) => Chainable<Subject>;
clearInput: (elementSelector: string) => Chainable<Subject>;
}
}
}
if (Cypress.env('SNAPSHOT')) {
command.addMatchImageSnapshotCommand({
failureThreshold: 0.01, // threshold for entire image
failureThresholdType: 'percent', // percent of image or number of pixels
});
} else {
Cypress.Commands.add('matchImageSnapshot', () => {
cy.log('skipping image snapshot');
});
}
Cypress.Commands.add('step', step);
Cypress.Commands.add('prefixedVisit', prefixedVisit);
Cypress.Commands.add('safeReload', safeReload);
Cypress.Commands.add('resetDb', { prevSubject: false }, resetDb);
// assertion helpers
Cypress.Commands.add('onboardingShouldLoad', onboardingShouldLoad);
Cypress.Commands.add('dashboardShouldLoad', dashboardShouldLoad);
Cypress.Commands.add('discoveryShouldFinish', discoveryShouldFinish);
Cypress.Commands.add('discoveryMightAppearAndShouldFinish', discoveryMightAppearAndShouldFinish);
// selector helpers
Cypress.Commands.add('getTestElement', getTestElement);
Cypress.Commands.add('getConfirmActionOnDeviceModal', getConfirmActionOnDeviceModal);
Cypress.Commands.add('hoverTestElement', hoverTestElement);
// various shortcuts
Cypress.Commands.add('toggleDeviceMenu', toggleDeviceMenu);
Cypress.Commands.add('enableDebugMode', enableDebugMode);
Cypress.Commands.add('disableFirmwareHashCheck', disableFirmwareHashCheck);
Cypress.Commands.add('toggleDebugModeInSettings', toggleDebugModeInSettings);
Cypress.Commands.add('passThroughInitialRun', passThroughInitialRun);
Cypress.Commands.add('passThroughAuthenticityCheck', passThroughAuthenticityCheck);
Cypress.Commands.add('passThroughBackup', passThroughBackup);
Cypress.Commands.add('passThroughBackupShamir', passThroughBackupShamir);
Cypress.Commands.add('passThroughInitMetadata', passThroughInitMetadata);
Cypress.Commands.add('passThroughSetPin', passThroughSetPin);
// @ts-expect-error
Cypress.Commands.add('enableRegtestAndGetCoins', enableRegtestAndGetCoins);
Cypress.Commands.add('text', { prevSubject: true }, subject => subject.text());
Cypress.Commands.add('createAccountFromMyAccounts', createAccountFromMyAccounts);
Cypress.Commands.add('interceptInvityApi', interceptInvityApi);
Cypress.Commands.add('interceptDataTrezorIo', interceptDataTrezorIo);
Cypress.Commands.add('findAnalyticsEventByType', findAnalyticsEventByType);
Cypress.Commands.add('enterPinOnBlindMatrix', enterPinOnBlindMatrix);
Cypress.Commands.add('addHiddenWallet', addHiddenWallet);
Cypress.Commands.add('changeViewOnlyState', changeViewOnlyState);
Cypress.Commands.add('clearInput', clearInput);

View File

@@ -1,3 +0,0 @@
export const languageMap = {
es: 'Español',
};

View File

@@ -1,4 +0,0 @@
export enum Currency {
EUR = 'eur',
USD = 'usd',
}

View File

@@ -1,3 +0,0 @@
export enum Language {
Spanish = 'es',
}

View File

@@ -1,4 +0,0 @@
export enum MetadataProvider {
Dropbox = 'dropbox',
Google = 'google',
}

View File

@@ -1,4 +0,0 @@
export enum SeedCheckType {
Advanced = 'advanced',
Standard = 'basic',
}

View File

@@ -1,4 +0,0 @@
export enum SeedType {
Default = 'single',
Advanced = 'shamir-advanced',
}

View File

@@ -1,5 +0,0 @@
export enum Theme {
System = 'system',
Dark = 'dark',
Light = 'light',
}

View File

@@ -1,13 +0,0 @@
import './commands';
import 'cypress-file-upload';
import 'cypress-real-events';
// Hide fetch/XHR requests
const app: any = window.top;
if (!app.document.head.querySelector('[data-hide-command-log-request]')) {
const style = app.document.createElement('style');
style.innerHTML =
'.command-name-request, .command-name-xhr, [class*="command-name-stub-"] { display: none }';
style.setAttribute('data-hide-command-log-request', '');
app.document.head.appendChild(style);
}

View File

@@ -1,103 +0,0 @@
/// <reference types="cypress" />
import { NetworkSymbol } from '@suite-common/wallet-config';
class AccountsPage {
clickAllAccountArrows() {
cy.getTestElement('@account-menu/arrow').click({ multiple: true });
}
openBtcAccount(index: number) {
cy.getTestElement(`@account-menu/btc/normal/${index}`).click();
}
applyCoinFilter(symbol: NetworkSymbol) {
cy.getTestElement(`@account-menu/filter/${symbol}`).as('account').click();
cy.get('@account').invoke('attr', 'data-test-activated').should('eq', 'true');
}
openAddAccountsModal() {
cy.getTestElement('@account-menu/add-account').click();
cy.getTestElement('@modal').should('be.visible');
}
activatNewCoin(symbol: NetworkSymbol) {
this.openAddAccountsModal();
cy.getTestElement('@modal/account/activate_more_coins').click();
cy.getTestElement(`@settings/wallet/network/${symbol}`).click();
cy.contains('button', 'Find my').click();
cy.getTestElement('@modal').should('not.exist');
cy.discoveryShouldFinish();
}
openSignandVerify() {
cy.getTestElement('@wallet/menu/extra-dropdown').click();
cy.getTestElement('@wallet/menu/wallet-sign-verify').click();
cy.getTestElement('@sign-verify/submit');
}
cycleThroughGraphspan() {
cy.contains('span', '1D').click({ force: true });
cy.get('[class*=InfoCard]').contains('1 day').should('be.visible');
cy.contains('span', '1W').click({ force: true });
cy.get('[class*=InfoCard]').contains('1 week').should('be.visible');
cy.contains('span', '1M').click({ force: true });
cy.get('[class*=InfoCard]').contains('1 month').should('be.visible');
cy.contains('span', '1Y').click({ force: true });
cy.get('[class*=InfoCard]').contains('1 year').should('be.visible');
cy.contains('span', 'All').click({ force: true });
cy.get('[class*=InfoCard]').contains('All').should('be.visible');
}
accountsPaginationCheck() {
cy.getTestElement('@wallet/accounts/pagination/5').click();
cy.getTestElement('@wallet/accounts/pagination/5')
.invoke('attr', 'data-test-activated')
.should('eq', 'true');
cy.getTestElement('@wallet/accounts/pagination/3').click();
cy.getTestElement('@wallet/accounts/pagination/3')
.invoke('attr', 'data-test-activated')
.should('eq', 'true');
}
searchLatestTxAddress() {
cy.getTestElement(
'@metadata/outputLabel/81d00a47d55b4df0b7a0793533c337493775ceb7f9ae20789325e25051f3374c-0/hover-container',
)
.find('span > span')
.invoke('text')
.then(notificationText => {
cy.log(notificationText);
cy.getTestElement('@wallet/accounts/search-icon').click({ force: true });
cy.getTestElement('@wallet/accounts/search-icon').type(notificationText);
cy.wait(500);
cy.getTestElement('@wallet/accounts/transaction-list')
.children()
.should('have.length', 2);
});
}
scrolltoBottomAccountspage() {
cy.getTestElement('@account-menu/legacy').click({ force: true });
cy.getTestElement('@account-menu/btc/legacy/0').click({ force: true });
cy.getTestElement('@app').scrollTo('bottom');
}
clickOnDesiredAccount(symbol: NetworkSymbol) {
cy.getTestElement(`@account-menu/${symbol}/normal/0`).click('left');
}
exportDesiredTransactionType(typeOfExport: string) {
cy.getTestElement('@wallet/accounts/export-transactions/dropdown').click({
scrollBehavior: 'center',
});
cy.getTestElement(`@wallet/accounts/export-transactions/${typeOfExport}`)
.should('be.visible')
.click({
scrollBehavior: 'center',
});
}
}
export const onAccountsPage = new AccountsPage();

View File

@@ -1,9 +0,0 @@
/// <reference types="cypress" />
class AnalyticsPage {
continue() {
cy.getTestElement('@analytics/continue-button').click();
}
}
export const onAnalyticsPage = new AnalyticsPage();

View File

@@ -1,18 +0,0 @@
/// <reference types="cypress" />
import { SeedCheckType } from '../enums/seedCheckType';
class CheckSeedPage {
initCheck(type: SeedCheckType, numOfWords: 12 | 24): void {
cy.getTestElement('@recovery/user-understands-checkbox').click();
cy.getTestElement('@recovery/start-button').click();
cy.getTestElement(`@recover/select-count/${numOfWords}`).click();
cy.getTestElement(`@recover/select-type/${type}`).click();
}
verifyCheckSuccessful(): void {
cy.getTestElement('@recovery/success-title').should('be.visible');
}
}
export const onCheckSeedPage = new CheckSeedPage();

View File

@@ -1,9 +0,0 @@
/// <reference types="cypress" />
class ConnectDevicePrompt {
waitForConnectDevicePrompt() {
cy.getTestElement('@connect-device-prompt', { timeout: 20000 }).should('be.visible');
}
}
export const onConnectDevicePrompt = new ConnectDevicePrompt();

View File

@@ -1,9 +0,0 @@
/// <reference types="cypress" />
class DeviceCompromisedModal {
ignoreDeviceCompromisedWarning() {
cy.getTestElement('@device-compromised/back-button').click();
}
}
export const onDeviceCompromisedModal = new DeviceCompromisedModal();

View File

@@ -1,9 +0,0 @@
/// <reference types="cypress" />
class Menu {
openSwitchDevice(): void {
cy.getTestElement('@menu/switch-device').click();
}
}
export const onMenu = new Menu();

View File

@@ -1,9 +0,0 @@
/// <reference types="cypress" />
class Modal {
close(): void {
cy.getTestElement('@modal/close-button').click();
}
}
export const onModal = new Modal();

View File

@@ -1,16 +0,0 @@
/// <reference types="cypress" />
class MultiShareBackupModal {
createMultiShareBackup(): void {
cy.getTestElement('@multi-share-backup/checkbox/1').click();
cy.getTestElement('@multi-share-backup/checkbox/2').click();
cy.getTestElement('@multi-share-backup/1st-info/submit-button').click();
cy.getTestElement('@multi-share-backup/2nd-info/submit-button').click();
}
finalizeMultiShareBackup(): void {
cy.getTestElement('@multi-share-backup/done/got-it-button').click();
}
}
export const onMultiShareBackupModal = new MultiShareBackupModal();

View File

@@ -1,76 +0,0 @@
/// <reference types="cypress" />
import { SeedType } from '../enums/seedType';
class OnboardingPage {
waitForConfirmationOnDevice() {
cy.getTestElement('@prompts/confirm-on-device').should('be.visible');
}
createWallet() {
cy.getTestElement('@onboarding/path-create-button').click();
}
selectSeedType(seedType: SeedType) {
cy.getTestElement('@onboarding/select-seed-type-open-dialog').click();
cy.getTestElement(`@onboarding/select-seed-type-${seedType}`).click();
cy.getTestElement('@onboarding/select-seed-type-confirm').click();
}
createBackup() {
cy.getTestElement('@onboarding/create-backup-button').click();
}
recoverWallet() {
cy.getTestElement('@onboarding/path-recovery-button').click();
}
continueRecovery() {
cy.getTestElement('@onboarding/recovery/continue-button').click();
}
continueCoins() {
cy.getTestElement('@onboarding/coins/continue-button').click();
}
startRecovery() {
cy.getTestElement('@onboarding/recovery/start-button').click();
}
retryRecovery() {
cy.getTestElement('@onboarding/recovery/retry-button').click();
}
continueFirmware() {
cy.getTestElement('@firmware/continue-button').click();
}
skipFirmware() {
cy.getTestElement('@firmware/skip-button').click();
cy.getTestElement('@onboarding/skip-button-confirm').click();
}
setPin() {
cy.getTestElement('@onboarding/set-pin-button').click();
}
continuePin() {
cy.getTestElement('@onboarding/pin/continue-button').click();
}
skipPin() {
cy.getTestElement('@onboarding/skip-button').click();
cy.getTestElement('@onboarding/skip-button-confirm').click();
}
skipTutorial() {
cy.getTestElement('@tutorial/skip-button').click();
cy.getTestElement('@tutorial/continue-button').click();
}
checkOnboardingSuccess() {
cy.getTestElement('@onboarding/final').should('be.visible').contains('Setup complete!');
}
}
export const onOnboardingPage = new OnboardingPage();

View File

@@ -1,13 +0,0 @@
/// <reference types="cypress" />
class RecoverPage {
selectWordCount(number: number) {
cy.getTestElement(`@recover/select-count/${number}`).click();
}
selectBasicRecovery() {
cy.getTestElement('@recover/select-type/basic').click();
}
}
export const onRecoverPage = new RecoverPage();

View File

@@ -1,12 +0,0 @@
/// <reference types="cypress" />
import { NetworkSymbol } from '@suite-common/wallet-config';
class SettingsCrypto {
activateCoin(symbol: NetworkSymbol) {
cy.getTestElement(`@settings/wallet/network/${symbol}`).as('selectedCoin').click();
cy.get('@selectedCoin').invoke('attr', 'data-active').should('be.eq', 'true');
}
}
export const onSettingsCryptoPage = new SettingsCrypto();

View File

@@ -1,31 +0,0 @@
/// <reference types="cypress" />
class SettingsDevicePage {
openSeedCheck(): void {
cy.getTestElement('@settings/device/check-seed-button').click();
}
openCreateMultiShareBackup(): void {
cy.getTestElement('@settings/device/create-multi-share-backup-button').click();
cy.getTestElement('@multi-share-backup/1st-info/submit-button').should('be.visible');
}
togglePinSwitch(): void {
cy.getTestElement('@settings/device/pin-switch').as('pinSwitch').scrollIntoView();
cy.get('@pinSwitch').should('be.visible');
cy.wait(500);
cy.get('@pinSwitch').click();
}
togglePassphraseSwitch(): void {
cy.getTestElement('@settings/device/passphrase-switch')
.as('passphraseSwitch')
.scrollIntoView();
cy.get('@passphraseSwitch').should('be.visible');
cy.get('@passphraseSwitch').click();
}
}
export const onSettingsDevicePage = new SettingsDevicePage();

View File

@@ -1,37 +0,0 @@
/// <reference types="cypress" />
import { languageMap } from '../../constants/languageMap';
import { Currency } from '../../enums/currency';
import { Language } from '../../enums/language';
import { Theme } from '../../enums/theme';
class SettingsGeneral {
changeFiatCurrency(currency: Currency) {
cy.getTestElement('@settings/fiat-select/input')
.should('be.visible')
.click({ scrollBehavior: 'bottom' });
cy.getTestElement(`@settings/fiat-select/option/${currency}`).click();
}
changeTheme(theme: Theme) {
cy.getTestElement('@theme/color-scheme-select/input')
.should('be.visible')
.click({ scrollBehavior: 'bottom' });
cy.getTestElement(`@theme/color-scheme-select/option/${theme}`).click();
cy.getTestElement('@theme/color-scheme-select/input').should(
'contain',
Cypress._.capitalize(theme),
);
}
changeLanguage(language: Language) {
cy.getTestElement('@settings/language-select/input').click({ scrollBehavior: 'bottom' });
cy.getTestElement(`@settings/language-select/option/${language}`).click({ force: true });
cy.getTestElement('@settings/language-select/input').should(
'contain',
languageMap[language],
);
}
}
export const onSettingGeneralPage = new SettingsGeneral();

View File

@@ -1,13 +0,0 @@
/// <reference types="cypress" />
class SettingsMenu {
openDeviceSettings(): void {
cy.getTestElement('@settings/menu/device').click();
}
openWalletSettings(): void {
cy.getTestElement('@settings/menu/wallet').click();
}
}
export const onSettingsMenu = new SettingsMenu();

View File

@@ -1,46 +0,0 @@
/// <reference types="cypress" />
class SuiteGuide {
openSidePanel() {
cy.getTestElement('@guide/button-open', { timeout: 20000 }).click();
cy.getTestElement('@guide/panel').should('exist');
}
openFeedback() {
cy.getTestElement('@guide/button-feedback').click();
}
openDesiredForm(formType: string) {
cy.getTestElement(`@guide/feedback/${formType}`).click();
}
selectLocationInApp(desiredLocation: string) {
cy.getTestElement('@guide/feedback/suggestion-dropdown').click();
cy.getTestElement(
`@guide/feedback/suggestion-dropdown/select/option/${desiredLocation.toLowerCase()}`,
).click();
}
fillInSuggestionForm(reportText: string) {
cy.getTestElement('@guide/feedback/suggestion-form').type(reportText);
}
submitForm() {
cy.getTestElement('@guide/feedback/submit-button').should('be.enabled').click();
}
sendBugreport({
desiredLocation,
reportText,
}: {
desiredLocation: string;
reportText: string;
}) {
this.openDesiredForm('bug');
this.selectLocationInApp(desiredLocation);
this.fillInSuggestionForm(reportText);
this.submitForm();
}
}
export const onSuiteGuide = new SuiteGuide();

View File

@@ -1,59 +0,0 @@
/// <reference types="cypress" />
class SwitchDevice {
private readonly walletSelectorBeginPart = '[data-testid^="@metadata/walletLabel/"]';
openDevice(index: number) {
cy.getTestElement(`@switch-device/wallet-on-index/${index}`).click();
}
getLabel(index: number): Cypress.Chainable<JQuery<HTMLElement>> {
return cy.get(`${this.walletSelectorBeginPart}[data-testid$=":${index + 1}"]`);
}
clickAddLabel(index: number) {
cy.wait(2000); // TODO fix waiting for animation
this.hoverAndCheck(
index,
`${this.walletSelectorBeginPart}[data-testid$=":${index + 1}/add-label-button"]`,
);
cy.get(
`${this.walletSelectorBeginPart}[data-testid$=":${index + 1}/add-label-button"]`,
).click();
}
clickEditLabel(index: number) {
cy.wait(2000); // TODO fix waiting for animation
this.hoverAndCheck(
index,
`${this.walletSelectorBeginPart}[data-testid$=":${index + 1}/edit-label-button"]`,
);
cy.get(
`${this.walletSelectorBeginPart}[data-testid$=":${index + 1}/edit-label-button"]`,
).click();
}
typeLabel(label: string) {
cy.getTestElement('@metadata/input').as('metadataInput').clear();
cy.get('@metadataInput').type(label);
cy.get('@metadataInput').type('{enter}');
}
private hoverAndCheck(index: number, checkSelector: string, retryCount = 2) {
if (retryCount === 0) {
throw new Error(`Failed to make the ${checkSelector} visible`);
}
cy.get(
`${this.walletSelectorBeginPart}[data-testid$=":${index + 1}/hover-container"]`,
).realHover();
cy.get(checkSelector).then($el => {
if (!$el.is(':visible')) {
cy.log(`Retrying hover and check for index ${index}`);
this.hoverAndCheck(index, checkSelector, retryCount - 1);
}
});
}
}
export const onSwitchDeviceModal = new SwitchDevice();

View File

@@ -1,19 +0,0 @@
/// <reference types="cypress" />
import { NetworkSymbol } from '@suite-common/wallet-config';
class NavBar {
openDefaultAcccount(symbol: NetworkSymbol = 'btc') {
cy.getTestElement(`@account-menu/${symbol}/normal/0`).click();
}
openSettings() {
cy.getTestElement('@suite/menu/settings', { timeout: 30000 })
.as('settingsButton')
.should('be.visible');
cy.get('@settingsButton').click();
cy.getTestElement('@settings/menu/general').should('be.visible');
}
}
export const onNavBar = new NavBar();

View File

@@ -1,24 +0,0 @@
/// <reference types="cypress" />
class WordInputPage {
inputWord(word: string) {
cy.getTestElement('@word-input-select/input').type(word);
cy.getTestElement(`@word-input-select/option/${word}`).click();
}
inputMnemonicT1B1(mnemonic: string[]) {
cy.step('Input mnemonic', () => {
for (let i = 0; i < 24; i++) {
cy.task('getDebugState').then(state => {
// @ts-expect-error
const position = state.recovery_word_pos - 1;
this.inputWord(mnemonic[position]);
});
cy.wait(500);
}
});
}
}
export const onWordInputPage = new WordInputPage();

View File

@@ -1,10 +0,0 @@
import { urlSearchParams } from '@trezor/suite/src/utils/suite/metadata';
import { SuiteAnalyticsEvent } from '@trezor/suite-analytics';
export type Requests = ReturnType<typeof urlSearchParams>[];
export type ExtractByEventType<EventType> = Extract<SuiteAnalyticsEvent, { type: EventType }>;
export type EventPayload<T extends SuiteAnalyticsEvent> = T extends { payload: infer P }
? P
: undefined;

View File

@@ -1,26 +0,0 @@
/**
* Use this method to ensure that app is in the first step of onboarding and
* ready to interact with
*/
export const onboardingShouldLoad = () =>
cy.getTestElement('@onboarding/welcome-step').should('be.visible');
export const dashboardShouldLoad = () => cy.getTestElement('@dashboard/index').should('be.visible');
export const discoveryShouldFinish = () => {
// todo: better waiting for discovery (mock it!)
cy.getTestElement('@wallet/discovery-progress-bar', { timeout: 45_000 });
cy.getTestElement('@wallet/discovery-progress-bar', { timeout: 45_000 }).should('not.exist');
};
/**
* This function is to be used when a discovery might appear (eg. when enabling some networks) and we need to wait for its finish
*/
export const discoveryMightAppearAndShouldFinish = () => {
cy.wait(1000); // after one second, discovery would have started if it was supposed to.
cy.get('body').then($body => {
if ($body.find('[data-testid="@wallet/discovery-progress-bar"]').length > 0) {
discoveryShouldFinish();
}
});
};

View File

@@ -1,12 +0,0 @@
export const fixtures = {
'/api/exchange/coins': '/invity/exchange/coins.json',
'/api/exchange/list': '/invity/exchange/list.json',
'/api/exchange/quotes': '/invity/exchange/quotes.json',
'/api/exchange/trade': '/invity/exchange/trade.json',
'/api/exchange/watch/0': '/invity/exchange/watch.json',
'/api/info': '/invity/buy/info.json',
'/api/v3/buy/list': '/invity/buy/list.json',
'/api/v3/buy/quotes': '/invity/buy/quotes.json',
'/api/v3/buy/trade': '/invity/buy/trade.json',
'/api/v3/buy/watch/0': '/invity/buy/watch.json',
};

View File

@@ -1,12 +0,0 @@
import { fixtures } from './fixtures';
export const interceptInvityApi = () => {
const InvityApiUrlToIntercept = 'https://exchange.trezor.io';
Object.entries(fixtures).forEach(fixtureEntry => {
const [path, fixture] = fixtureEntry;
cy.intercept(`${InvityApiUrlToIntercept}${path}`, {
fixture,
});
});
};

View File

@@ -1,22 +0,0 @@
/**
* Just like cy.get() but will return element specified with 'data-testid=' attribute.
*
* @example any element <div data=test="my-fancy-attr-name" />
* cy.getTestElement('my-fancy-attr-name')
*
* @example example Select element
* <Select data-testid="send/fee-select" options=[{label: 'foo', value: 'bla'}] />
*
* - will get input
* getTestElement('send/fee-select/input')
*
* - will get option by its value (something in example)
* getTestElement('send/fee-select/option/something')
*/
export const getTestElement = (selector: string, options?: Parameters<typeof cy.get>[1]) =>
cy.get(`[data-testid="${selector}"]`, options);
export const getConfirmActionOnDeviceModal = () => cy.getTestElement('@prompts/confirm-on-device');
export const hoverTestElement = (selector: string) => cy.getTestElement(selector).realHover();

View File

@@ -1,239 +0,0 @@
import { SUITE as SuiteActions } from '@trezor/suite/src/actions/suite/constants';
import { urlSearchParams } from '@trezor/suite/src/utils/suite/metadata';
import { SuiteAnalyticsEvent } from '@trezor/suite-analytics';
import { onNavBar } from '../pageObjects/topBarObject';
import { EventPayload, Requests } from '../types';
/**
* Shortcut to click device menu
*/
export const toggleDeviceMenu = () => cy.getTestElement('@menu/switch-device').click();
export const passThroughInitialRun = ({ viewOnly = true } = {}) => {
cy.disableFirmwareHashCheck();
cy.getTestElement('@analytics/continue-button', { timeout: 30_000 }).click();
cy.getTestElement('@onboarding/exit-app-button').click();
if (viewOnly) {
cy.getTestElement('@onboarding/viewOnly/enable').click();
} else {
cy.getTestElement('@onboarding/viewOnly/skip').click();
}
cy.getTestElement('@viewOnlyTooltip/gotIt', { timeout: 10_000 }).click();
};
export const passThroughAuthenticityCheck = () => {
// enable debug mode to allow debug keys for authenticity check
cy.enableDebugMode();
cy.getTestElement('@authenticity-check/start-button').click();
cy.getTestElement('@prompts/confirm-on-device');
cy.task('pressYes');
cy.getTestElement('@authenticity-check/continue-button').click();
};
export const passThroughBackup = () => {
// todo: much of commented out code probably stays in standalone backup?
cy.log('Backup button should be disabled until all checkboxes are checked');
cy.getTestElement('@backup/start-button').should('be.disabled');
cy.getTestElement('@backup/check-item/wrote-seed-properly').click();
cy.getTestElement('@backup/check-item/made-no-digital-copy').click();
cy.getTestElement('@backup/check-item/will-hide-seed').click();
cy.log('Create backup on device');
cy.getTestElement('@backup/start-button').click();
// cy.getConfirmActionOnDeviceModal();
cy.getTestElement('@prompts/confirm-on-device');
cy.task('readAndConfirmMnemonicEmu');
cy.getTestElement('@backup/close-button').click();
};
export const passThroughBackupShamir = (shares: number, threshold: number) => {
cy.log('Backup button should be disabled until all checkboxes are checked');
cy.getTestElement('@backup/start-button').should('be.disabled');
cy.getTestElement('@backup/check-item/wrote-seed-properly').click();
cy.getTestElement('@backup/check-item/made-no-digital-copy').click();
cy.getTestElement('@backup/check-item/will-hide-seed').click();
cy.log('Create Shamir backup on device');
cy.getTestElement('@backup/start-button').click();
cy.getTestElement('@prompts/confirm-on-device');
cy.wait(1000); // It seems that there is a race condition and the next task fails sometimes, because it hits the homescreen instead of the expected one
cy.task('readAndConfirmShamirMnemonicEmu', { shares, threshold });
cy.getTestElement('@backup/close-button').click();
};
export const passThroughInitMetadata = (provider: 'dropbox' | 'google') => {
cy.getConfirmActionOnDeviceModal();
cy.task('pressYes');
cy.getTestElement(`@modal/metadata-provider/${provider}-button`).click();
cy.getTestElement('@modal/metadata-provider').should('not.exist');
};
export const passThroughSetPin = () => {
cy.getTestElement('@onboarding/set-pin-button').click();
cy.getTestElement('@prompts/confirm-on-device');
cy.task('pressYes');
cy.task('inputEmu', '1');
cy.task('inputEmu', '1');
cy.task('pressYes');
cy.getTestElement('@onboarding/pin/continue-button').click();
};
export const enableDebugMode = () => {
cy.window().then(window => {
window.store.dispatch({
type: SuiteActions.SET_DEBUG_MODE,
payload: { showDebugMenu: true },
});
});
};
export const disableFirmwareHashCheck = () => {
// window.store may not be ready at this point, we need to wait for app to load
cy.getTestElement('@welcome-layout/body');
cy.window().then(window => {
window.store.dispatch({
type: SuiteActions.TOGGLE_FIRMWARE_HASH_CHECK,
payload: false,
});
});
};
export const toggleDebugModeInSettings = () => {
const timesClickToSetDebugMode = 5;
for (let i = 0; i < timesClickToSetDebugMode; i++) {
cy.getTestElement('@settings/menu/title').click();
}
};
export const enableRegtestAndGetCoins = ({ payments = [] }) => {
onNavBar.openSettings();
cy.getTestElement('@settings/menu/wallet').click();
cy.toggleDebugModeInSettings();
cy.getTestElement('@settings/wallet/network/regtest').click({ force: true });
// send 1 regtest bitcoin to first address in the derivation path
payments.forEach(payment => {
cy.task('sendToAddressAndMineBlock', {
// @ts-expect-error
address: payment.address,
// @ts-expect-error
btc_amount: payment.amount,
});
});
cy.task('mineBlocks', { block_amount: 1 });
};
export const createAccountFromMyAccounts = (coin: string, label: string) => {
cy.getTestElement('@wallet/discovery-progress-bar', { timeout: 30000 }).should('not.exist');
cy.getTestElement('@account-menu/add-account').click();
cy.getTestElement('@modal').should('be.visible');
cy.get(`[data-testid="@settings/wallet/network/${coin}"]`).click();
cy.getTestElement('@add-account-type/select/input').click();
cy.get(`[data-testid="@add-account-type/select/option/${label}"]`).click();
cy.getTestElement('@add-account').click();
};
export const interceptDataTrezorIo = (requests: Requests) =>
cy.intercept({ hostname: 'data.trezor.io', url: '/suite/log/**' }, req => {
const params = urlSearchParams(req.url);
requests.push(params);
});
export const findAnalyticsEventByType = <T extends SuiteAnalyticsEvent>(
requests: Requests,
eventType: T['type'],
): Cypress.Chainable<NonNullable<EventPayload<T>>> =>
cy.wrap(requests).then(requestsArr => {
const event = requestsArr.find(req => req.c_type === eventType) as EventPayload<T>;
if (!event) {
throw new Error(`Event with type ${eventType} not found.`);
}
return event;
});
export const enterPinOnBlindMatrix = (pinEntryNumber: string) => {
cy.task('getDebugState').then(state => {
// TODO: export and take types from @trezor/user-env-link
// @ts-expect-error
const index = state.matrix.indexOf(pinEntryNumber) + 1;
cy.getTestElement(`@pin/input/${index}`).click();
cy.getTestElement('@pin/submit-button').click();
});
};
export const addHiddenWallet = (passphrase: string) => {
cy.getTestElement('@switch-device/add-hidden-wallet-button').click();
cy.getTestElement('@passphrase/input').type(passphrase);
cy.getTestElement('@passphrase/hidden/submit-button').click();
cy.task('pressYes');
cy.task('pressYes');
cy.getTestElement('@passphrase-confirmation/step1-open-unused-wallet-button', {
timeout: 15_000,
}).click();
cy.getTestElement('@passphrase-confirmation/step2-button').click();
cy.getTestElement('@passphrase/input', { timeout: 10000 }).type(passphrase);
cy.getTestElement('@passphrase/hidden/submit-button').click();
cy.task('pressYes');
cy.task('pressYes');
cy.getTestElement('@dashboard/loading').should('not.exist');
};
export const changeViewOnlyState = (walletIndex: number, desiredState: 'enabled' | 'disabled') => {
// get the wallet container
cy.getTestElement(`@switch-device/wallet-on-index/${walletIndex}`).then(walletContainer => {
// check if change is even necessary
if (!walletContainer.find(`[data-testid="@viewOnlyStatus/${desiredState}"]`).length) {
// if it is, open view-only settings container and change the state
cy.wrap(walletContainer)
.find('[data-testid="@collapsible-box/icon-collapsed"]')
.click();
cy.wrap(walletContainer)
.find('[data-testid="@collapsible-box/body"]')
.should('be.visible');
cy.wrap(walletContainer).find(`[data-testid$="/${desiredState}"]`).click();
// close it to match the initial state
cy.wrap(walletContainer).find('[data-testid="@collapsible-box/icon-expanded"]').click();
}
});
};
export const clearInput = (elementSelector: string) => {
cy.getTestElement(elementSelector).then(element => {
// get number of characters in the input
const charCount = element.attr('value')?.length ?? 0;
cy.getTestElement(elementSelector).type('{rightArrow}'.repeat(charCount));
for (let i = 0; i < charCount; i++) {
cy.getTestElement(elementSelector).type('{backspace}');
}
});
};
export const forceLightMode = {
onBeforeLoad(win: Window): void {
const matchMediaStub = cy.stub().callsFake(query => ({
matches: query === '(prefers-color-scheme: light)',
media: query,
onchange: null,
addListener: cy.stub(),
removeListener: cy.stub(),
addEventListener: cy.stub(),
removeEventListener: cy.stub(),
dispatchEvent: cy.stub(),
}));
cy.stub(win, 'matchMedia').callsFake(matchMediaStub);
},
};

View File

@@ -1,16 +0,0 @@
/**
* Clears database. Use it to avoid persistence between tests
*
* @example cy.resetDb()
*/
export const resetDb = () => {
const request = indexedDB.deleteDatabase('trezor-suite');
request.onerror = function () {
return cy;
};
request.onsuccess = function () {
return cy;
};
};

View File

@@ -1,17 +0,0 @@
{
// "extends": "../tsconfig.json",
"compilerOptions": {
"types": [
"cypress",
"node",
"cypress-real-events"
],
"module": "esnext",
"moduleResolution": "node",
"target": "ES6",
"composite": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"references": [{ "path": "../../e2e-utils" }]
}

View File

@@ -1,16 +1,10 @@
import pluginCypress from 'eslint-plugin-cypress/flat';
import { eslint } from '@trezor/eslint';
export default [
...eslint,
pluginCypress.configs.recommended,
{
rules: {
'jest/valid-expect': 'off', // because of cypress tests
'import/no-default-export': 'off', // Todo: fix and solve
'no-console': 'off', // It's used in cypress tests
'cypress/no-unnecessary-waiting': 'off', // A lot of arbitrary waiting is needed in Cypress tests
},
},
];

View File

@@ -18,7 +18,6 @@
"@trezor/connect": "workspace:*",
"@trezor/device-utils": "workspace:*",
"@trezor/suite": "workspace:*",
"cypress-real-events": "^1.11.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-helmet-async": "^2.0.5",
@@ -40,15 +39,10 @@
"@trezor/transport": "workspace:*",
"@trezor/trezor-user-env-link": "workspace:*",
"@trezor/utils": "workspace:*",
"@types/cypress-image-snapshot": "^3.1.8",
"@types/react": "18.2.79",
"@types/react-dom": "18.2.19",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.1.7",
"cypress": "^13.6.4",
"cypress-file-upload": "^5.0.8",
"cypress-image-snapshot": "^4.0.1",
"eslint-plugin-cypress": "^4.1.0",
"rimraf": "^6.0.1",
"stylelint": "^16.14.1",
"stylelint-config-standard": "^36.0.0",

View File

@@ -2,13 +2,6 @@
@playwright/browser-firefox
@playwright/browser-webkit
@playwright/test
cypress
cypress-file-upload
cypress-image-snapshot
cypress-real-events
@currents/playwright
@types/cypress-image-snapshot
eslint-plugin-cypress
@currents/cmd
jest-junit
@types/lodash

740
yarn.lock

File diff suppressed because it is too large Load Diff