Integration tests (#311)

* Add base test env

* Add eslint rules for cypress

* Add configs and scripts in package json

* Added docker file for bridge and emualator

* Bridge install progress

* Bridge install next step

* Add task for integration tests

* Fixed deps

* Added baseUrl

* Added baseUrl fix

* Added npx

* Added caching for cypress bin

* Added path to binary

* Install cypress

* Finalized dockerfile

* Fixed bridge lib path

* Fixed path for binary

* Adjust script again

* Run all the things properly

* Try to run the tests

* First POC test

* First POC test in gitlab

* Fixed flow

* Fixed gitlab test url, try docker service

* export artifacts

* Test only integration tests in CI

* Test only integration tests in CI 2

* Test only integration tests in CI 3

* Added tests for initialize device

* Try to add docker in only one step

* Turn on other integration steps

* Correct node version

* Ignore cache in flow

* Run bridge and emulator in debug link mode

* Fix param

* Try to run new config in CI

* init device in docker

* Remove docker image after run

* Remove amp

* Fix path

* Artifacts on fail

* Artifacts on fail with volume

* Artifacts on fail with volume 2

* Install mkdir

* Install mkdir again

* test

* test 2

* test 3

* test 4

* test 5

* test 6

* test 7

* test 8

* test 9

* test 10

* test 11

* test 12

* test 13

* test 14

* test 15

* test 16

* test 17

* Revert "test 17"

This reverts commit f3f6c0d690.

* test 18

* test 19

* test 20

* test 21 try chrome

* test 22

* test 23

* test 24

* test 25

* test 25 try to install chrome again

* test 25 try to install chrome again

* Added missing deps

* Added debug

* Install chromium

* Install chromium 2

* turn on chromium

* turn off debug

* turn on debug

* fix folder

* turn off debug

* Fix init device

* Add header dashboard test

* Bring things back

* clean

* clean fix

* Build image in CI

* Added stage step

* Added docker image

* Added service

* Added tests to docker image

* Refactor a bit

* Correct registry image

* Build wallet again

* Add test for dashbaord content

* new node version, more tests

* Remove unused code

* typo

* Correct snapshots, moved deps to dev, beta disclaimer prop
pull/353/head
Vladimir Volek 5 years ago committed by Maroš
parent 02a976a759
commit 46fe6d00fc

@ -10,7 +10,8 @@
}, },
"env": { "env": {
"browser": true, "browser": true,
"jest": true "jest": true,
"cypress/globals": true
}, },
"rules": { "rules": {
"import/prefer-default-export": 0, "import/prefer-default-export": 0,
@ -32,13 +33,17 @@
"new-cap": 0, "new-cap": 0,
"max-len": 0, "max-len": 0,
"eol-last": 0, "eol-last": 0,
"spaced-comment": 0 "spaced-comment": 0,
"no-unused-expressions": 0,
"chai-friendly/no-unused-expressions": 2
}, },
"plugins": [ "plugins": [
"import", "import",
"react", "react",
"jest", "jest",
"flowtype" "flowtype",
"cypress",
"chai-friendly"
], ],
"settings": { "settings": {
"import/resolver": { "import/resolver": {

@ -10,6 +10,7 @@
.*/_old/.* .*/_old/.*
.*/scripts/solidity/.* .*/scripts/solidity/.*
.*/build/.* .*/build/.*
.*/cache/.*
[libs] [libs]
./src/flowtype/npm/redux_v3.x.x.js ./src/flowtype/npm/redux_v3.x.x.js

4
.gitignore vendored

@ -19,4 +19,6 @@ logs
_old _old
coverage coverage
test/**/__diff_output__
test/screenshots

@ -1,14 +1,19 @@
image: node:9.3 image: node:10.15.1
variables:
CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/cache/Cypress"
cache: cache:
key: ${CI_COMMIT_REF_SLUG} key: ${CI_COMMIT_REF_SLUG}
paths: paths:
- node_modules/ - node_modules
- ${CYPRESS_CACHE_FOLDER}
stages: stages:
- test - test
- build - build
- deploy - deploy
- integration tests
lint: lint:
stage: test stage: test
@ -62,7 +67,23 @@ build stable:
expire_in: 1 week expire_in: 1 week
paths: paths:
- build/stable - build/stable
- scripts/s3sync.sh - scripts/s3sync.sh
build emulator and bridge image:
variables:
CONTAINER_NAME: "$CI_REGISTRY/emulator-bridge-tests"
image: docker:latest
services:
- docker:dind
before_script:
- docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD
stage: build
when: manual
script:
- docker pull $CONTAINER_NAME:latest || true
- docker build --cache-from $CONTAINER_NAME:latest --tag $CONTAINER_NAME:$CI_COMMIT_SHA --tag $CONTAINER_NAME:latest .
- docker push $CONTAINER_NAME:$CI_COMMIT_SHA
- docker push $CONTAINER_NAME:latest
deploy review: deploy review:
stage: deploy stage: deploy
@ -138,3 +159,24 @@ delete review:
- branches - branches
tags: tags:
- deploy - deploy
integration tests:
image: docker:latest
services:
- docker:dind
stage: integration tests
script:
- 'export SHARED_PATH="$(dirname ${CI_PROJECT_DIR})/shared"'
- rm -r ${SHARED_PATH} || true
- docker build -t wallet-emulator-bridge-tests .
- mkdir -p ${SHARED_PATH}/trezor-wallet/screenshots
- mkdir -p ${SHARED_PATH}/trezor-wallet/videos
- docker run --volume ${SHARED_PATH}/trezor-wallet/screenshots:/trezor-wallet/test/screenshots --volume ${SHARED_PATH}/trezor-wallet/videos:/trezor-wallet/test/videos --rm wallet-emulator-bridge-tests
- find ${SHARED_PATH}
- mkdir trezor-wallet
- cp -r ${SHARED_PATH}/ trezor-wallet/
artifacts:
when: always
expire_in: 1 week
paths:
- trezor-wallet/

@ -1,17 +1,50 @@
FROM node:9.3 FROM python:latest
ARG BUILD_TYPE=stable #
# setup
#
RUN apt-get update
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash -
RUN apt-get install -y chromium libappindicator3-1 xdg-utils fonts-liberation nodejs wget dpkg git python python3 python3-pip xvfb libgtk2.0-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2
RUN npm install -g yarn
WORKDIR /trezor-wallet-app RUN ln -s /usr/bin/chromium /usr/local/bin/chromium-browser
COPY package.json /trezor-wallet-app #
COPY yarn.lock /trezor-wallet-app # build emulator
#
RUN mkdir /trezor-emulator
WORKDIR /trezor-emulator
RUN yarn install RUN git clone https://github.com/trezor/trezor-core
WORKDIR /trezor-emulator/trezor-core
RUN git submodule update --init --recursive
COPY . /trezor-wallet-app RUN apt-get install libusb-1.0-0
RUN pip3 install scons trezor
RUN make build_unix_noui
RUN yarn run build:${BUILD_TYPE} #
# install bridge
#
RUN mkdir /trezor-bridge
WORKDIR /trezor-bridge
RUN wget https://wallet.trezor.io/data/bridge/2.0.25/trezor-bridge_2.0.25_amd64.deb
RUN dpkg -x /trezor-bridge/trezor-bridge_2.0.25_amd64.deb /trezor-bridge/extracted
EXPOSE 8080 #
CMD [ "yarn", "run", "prod-server" ] # install trezor-wallet
#
RUN mkdir /trezor-wallet
WORKDIR /trezor-wallet
COPY package.json /trezor-wallet
COPY yarn.lock /trezor-wallet
RUN yarn
COPY . /trezor-wallet
RUN yarn run build:stable
#
# run
#
ENTRYPOINT ["/trezor-wallet/test/scripts/run-all.sh"]
EXPOSE 8080 21325

@ -0,0 +1,11 @@
{
"integrationFolder": "test/integration",
"fixturesFolder": "test/fixtures",
"pluginsFile": "test/plugins/index.js",
"supportFile": "test/support/index.js",
"defaultCommandTimeout": 10000,
"screenshotsFolder": "test/screenshots",
"video": false,
"trashAssetsBeforeRuns": true,
"chromeWebSecurity": false
}

@ -21,10 +21,14 @@
"test": "run-s test:*", "test": "run-s test:*",
"test:unit": "npx jest", "test:unit": "npx jest",
"test-unit:watch": "npx jest -o --watch", "test-unit:watch": "npx jest -o --watch",
"test-integration:dev": "npx cypress open -c baseUrl=http://localhost:8081/#/",
"test-integration:test": "npx cypress run",
"test-integration:gitlab": "npx cypress run -c baseUrl=https://localhost:8080/#/ --browser chromium",
"server:beta": "node ./server/index.js --buildType=beta", "server:beta": "node ./server/index.js --buildType=beta",
"server:stable": "node ./server/index.js --buildType=stable" "server:stable": "node ./server/index.js --buildType=stable"
}, },
"dependencies": { "dependencies": {
"@babel/polyfill": "^7.2.5",
"babel": "^6.23.0", "babel": "^6.23.0",
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
"bignumber.js": "2.4.0", "bignumber.js": "2.4.0",
@ -69,9 +73,11 @@
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-raven-middleware": "^1.2.0", "redux-raven-middleware": "^1.2.0",
"redux-thunk": "^2.2.0", "redux-thunk": "^2.2.0",
"request": "^2.88.0",
"rimraf": "^2.6.2", "rimraf": "^2.6.2",
"styled-components": "^4.1.2", "styled-components": "^4.1.2",
"styled-normalize": "^8.0.4", "styled-normalize": "^8.0.4",
"trezor-bridge-communicator": "1.0.2",
"trezor-connect": "7.0.0-beta.2", "trezor-connect": "7.0.0-beta.2",
"wallet-address-validator": "^0.2.4", "wallet-address-validator": "^0.2.4",
"web3": "1.0.0-beta.35", "web3": "1.0.0-beta.35",
@ -94,10 +100,14 @@
"babel-preset-env": "^1.6.0", "babel-preset-env": "^1.6.0",
"babel-preset-jest": "^23.2.0", "babel-preset-jest": "^23.2.0",
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.24.1",
"cypress": "^3.1.5",
"cypress-image-snapshot": "^3.0.0",
"eslint": "^4", "eslint": "^4",
"eslint-config-airbnb": "^17.0.0", "eslint-config-airbnb": "^17.0.0",
"eslint-import-resolver-babel-module": "^4.0.0", "eslint-import-resolver-babel-module": "^4.0.0",
"eslint-loader": "^2.1.0", "eslint-loader": "^2.1.0",
"eslint-plugin-chai-friendly": "^0.4.1",
"eslint-plugin-cypress": "^2.2.0",
"eslint-plugin-flowtype": "^2.50.0", "eslint-plugin-flowtype": "^2.50.0",
"eslint-plugin-import": "^2.14.0", "eslint-plugin-import": "^2.14.0",
"eslint-plugin-jest": "^21.18.0", "eslint-plugin-jest": "^21.18.0",

@ -17,6 +17,7 @@ type Props = {
isWhite?: boolean, isWhite?: boolean,
isWebUsb?: boolean, isWebUsb?: boolean,
isTransparent?: boolean, isTransparent?: boolean,
dataTest?: string
} }
const Wrapper = styled.button` const Wrapper = styled.button`
@ -146,10 +147,12 @@ const Button = ({
isWhite = false, isWhite = false,
isWebUsb = false, isWebUsb = false,
isTransparent = false, isTransparent = false,
dataTest,
}: Props) => { }: Props) => {
const newClassName = isWebUsb ? `${className} trezor-webusb-button` : className; const newClassName = isWebUsb ? `${className} trezor-webusb-button` : className;
return ( return (
<Wrapper <Wrapper
data-test={dataTest}
className={newClassName} className={newClassName}
onClick={onClick} onClick={onClick}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
@ -176,6 +179,7 @@ Button.propTypes = {
isWhite: PropTypes.bool, isWhite: PropTypes.bool,
isWebUsb: PropTypes.bool, isWebUsb: PropTypes.bool,
isTransparent: PropTypes.bool, isTransparent: PropTypes.bool,
dataTest: PropTypes.string,
}; };
export default Button; export default Button;

@ -98,11 +98,13 @@ const DeviceHeader = ({
disabled = false, disabled = false,
isSelected = false, isSelected = false,
className, className,
testId,
}) => { }) => {
const status = getStatus(device); const status = getStatus(device);
return ( return (
<Wrapper <Wrapper
isSelected={isSelected} isSelected={isSelected}
data-test={testId}
isOpen={isOpen} isOpen={isOpen}
isHoverable={isHoverable} isHoverable={isHoverable}
disabled={disabled} disabled={disabled}
@ -134,6 +136,7 @@ DeviceHeader.propTypes = {
isSelected: PropTypes.bool, isSelected: PropTypes.bool,
onClickWrapper: PropTypes.func.isRequired, onClickWrapper: PropTypes.func.isRequired,
className: PropTypes.string, className: PropTypes.string,
testId: PropTypes.string,
}; };
export default DeviceHeader; export default DeviceHeader;

@ -112,7 +112,7 @@ type Props = {
}; };
const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => ( const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
<Wrapper> <Wrapper data-test="Main__page__navigation">
<LayoutWrapper> <LayoutWrapper>
<Left> <Left>
{ sidebarEnabled && <MenuToggler onClick={toggleSidebar}>{sidebarOpened ? '✕ Close' : '☰ Menu'}</MenuToggler>} { sidebarEnabled && <MenuToggler onClick={toggleSidebar}>{sidebarOpened ? '✕ Close' : '☰ Menu'}</MenuToggler>}

@ -68,7 +68,7 @@ const BetaDisclaimer = (props: { close: () => void }) => (
/> />
Please note that the <i>Trezor Beta Wallet</i> might be collecting anonymized usage data, especially error logs, for development purposes. The <i>Trezor Wallet</i> does not log any data. Please note that the <i>Trezor Beta Wallet</i> might be collecting anonymized usage data, especially error logs, for development purposes. The <i>Trezor Wallet</i> does not log any data.
</StyledP> </StyledP>
<StyledButton onClick={props.close}>OK, I understand</StyledButton> <StyledButton dataTest="Modal__disclaimer__button__confirm" onClick={props.close}>OK, I understand</StyledButton>
</ModalWindow> </ModalWindow>
</Wrapper> </Wrapper>
); );

@ -57,7 +57,7 @@ class CoinMenu extends PureComponent<Props> {
render() { render() {
const { config } = this.props.localStorage; const { config } = this.props.localStorage;
return ( return (
<Wrapper> <Wrapper data-test="Main__page__coin__menu">
{config.networks.map(item => ( {config.networks.map(item => (
<NavLink <NavLink
key={item.shortcut} key={item.shortcut}
@ -72,6 +72,7 @@ class CoinMenu extends PureComponent<Props> {
</NavLink> </NavLink>
))} ))}
<Divider <Divider
testId="Main__page__coin__menu__divider"
textLeft="Other coins" textLeft="Other coins"
textRight="(You will be redirected)" textRight="(You will be redirected)"
hasBorder hasBorder

@ -23,9 +23,10 @@ const TextLeft = styled.p`
`; `;
const Divider = ({ const Divider = ({
textLeft, textRight, hasBorder = false, className, textLeft, textRight, hasBorder = false, className, testId,
}) => ( }) => (
<Wrapper <Wrapper
data-test={testId}
hasBorder={hasBorder} hasBorder={hasBorder}
className={className} className={className}
> >
@ -39,6 +40,7 @@ Divider.propTypes = {
textLeft: PropTypes.string, textLeft: PropTypes.string,
textRight: PropTypes.string, textRight: PropTypes.string,
hasBorder: PropTypes.bool, hasBorder: PropTypes.bool,
testId: PropTypes.string,
}; };
export default Divider; export default Divider;

@ -207,6 +207,7 @@ class LeftNavigation extends React.PureComponent<Props, State> {
<Sidebar isOpen={props.wallet.showSidebar}> <Sidebar isOpen={props.wallet.showSidebar}>
<Header <Header
isSelected isSelected
testId="Main__page__device__header"
isHoverable={false} isHoverable={false}
onClickWrapper={() => { onClickWrapper={() => {
if (isDeviceAccessible || this.props.devices.length > 1) { if (isDeviceAccessible || this.props.devices.length > 1) {
@ -237,7 +238,7 @@ class LeftNavigation extends React.PureComponent<Props, State> {
{dropdownOpened && <DeviceMenu ref={this.deviceMenuRef} {...this.props} />} {dropdownOpened && <DeviceMenu ref={this.deviceMenuRef} {...this.props} />}
{isDeviceAccessible && menu} {isDeviceAccessible && menu}
</Body> </Body>
<Footer key="sticky-footer"> <Footer data-test="Main__page__footer" key="sticky-footer">
<Help> <Help>
<A <A
href="https://trezor.io/support/" href="https://trezor.io/support/"

@ -114,7 +114,11 @@ const StyledBackdrop = styled(Backdrop)`
const Wallet = (props: Props) => ( const Wallet = (props: Props) => (
<AppWrapper> <AppWrapper>
<Header sidebarEnabled={!!props.wallet.selectedDevice} sidebarOpened={props.wallet.showSidebar} toggleSidebar={props.toggleSidebar} /> <Header
sidebarEnabled={!!props.wallet.selectedDevice}
sidebarOpened={props.wallet.showSidebar}
toggleSidebar={props.toggleSidebar}
/>
<AppNotifications /> <AppNotifications />
<WalletWrapper> <WalletWrapper>
<StyledBackdrop show={props.wallet.showSidebar} onClick={props.toggleSidebar} animated /> <StyledBackdrop show={props.wallet.showSidebar} onClick={props.toggleSidebar} animated />

@ -49,7 +49,7 @@ const Image = styled.img`
const Dashboard = () => ( const Dashboard = () => (
<Content> <Content>
<Wrapper> <Wrapper>
<Row> <Row data-test="Dashboard__page__content">
<H1>Please select your coin</H1> <H1>Please select your coin</H1>
<StyledP>You will gain access to receiving &amp; sending selected coin</StyledP> <StyledP>You will gain access to receiving &amp; sending selected coin</StyledP>
<Overlay> <Overlay>

@ -26,7 +26,7 @@ const StyledParagraph = styled(Paragraph)`
`; `;
const Initialize = () => ( const Initialize = () => (
<Wrapper> <Wrapper data-test="Page__device__not__initialized">
<Row> <Row>
<H1>Your device is not initialized</H1> <H1>Your device is not initialized</H1>
<StyledParagraph>Please use Bitcoin wallet interface to start initialization process</StyledParagraph> <StyledParagraph>Please use Bitcoin wallet interface to start initialization process</StyledParagraph>

@ -0,0 +1,26 @@
describe('Dashboard page', () => {
beforeEach(() => {
cy.viewport(1366, 1800);
cy.visit('/');
});
it('navigation', () => {
cy.getTestElement('Main__page__navigation')
.should('be.visible')
.matchImageSnapshot();
});
it('content', () => {
cy.getTestElement('Dashboard__page__content')
.should('be.visible')
.matchImageSnapshot();
});
// Menu
it('device header', () => {
cy.getTestElement('Main__page__device__header')
.should('be.visible')
.matchImageSnapshot();
});
});

@ -0,0 +1,7 @@
const {
addMatchImageSnapshotPlugin,
} = require('cypress-image-snapshot/plugin');
module.exports = (on) => {
addMatchImageSnapshotPlugin(on);
};

@ -0,0 +1,6 @@
import '@babel/polyfill';
import { initSeedAllDevice } from 'trezor-bridge-communicator';
(async () => {
await initSeedAllDevice();
})();

@ -0,0 +1,20 @@
#!/bin/bash
# go to root
cd "$(dirname "$0")"
cd ..
# run bridge
cd /trezor-bridge && ./extracted/usr/bin/trezord -ed 21324:21325 -u=false &
# run emulator
cd /trezor-emulator/trezor-core && PYOPT=0 ./emu.sh &
# run wallet
cd /trezor-wallet && yarn run server:stable &
# init device
npx babel-node /trezor-wallet/test/scripts/init-device.js &
# run tests
yarn run test-integration:gitlab -c baseUrl="https://localhost:8080/#/"

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@ -0,0 +1,5 @@
import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command';
addMatchImageSnapshotCommand();
Cypress.Commands.add('getTestElement', selector => cy.get(`[data-test="${selector}"]`));

@ -0,0 +1,5 @@
import './commands';
beforeEach(() => {
window.localStorage.setItem('/betaModalPrivacy', true);
});

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save