diff --git a/.eslintignore b/.eslintignore index 23fdd0c543..10c9718f18 100755 --- a/.eslintignore +++ b/.eslintignore @@ -5,8 +5,9 @@ main.js logs node_modules translations -./tests -features/tests/e2e/documents/* +tests +tests/paper-wallets/e2e/documents/* +tests/wallets/e2e/documents/* mainnet-genesis-dryrun-with-stakeholders.json source/renderer/app/i18n/locales nodemon.json diff --git a/.gitignore b/.gitignore index 889975bc75..1727a3e3ac 100755 --- a/.gitignore +++ b/.gitignore @@ -62,8 +62,8 @@ translations/messages translations/reports # 'Screenshots' and 'Paper wallet ceritifcate PDF file' generated by acceptance tests -features/tests/e2e/documents/paper_wallet_certificates/paper-wallet-certificate.pdf -features/tests/e2e/screenshots +tests/paper-wallets/e2e/documents/paper-wallet-certificate.pdf +tests/screenshots/ # Webpack .cache diff --git a/.prettierignore b/.prettierignore index 583be1c748..4c9d735a7b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -20,4 +20,5 @@ # Ignore the following special folders and files source/renderer/app/i18n/locales/ -features/tests/e2e/documents/* +tests/paper-wallets/e2e/documents/* +tests/wallets/e2e/documents/* diff --git a/CHANGELOG.md b/CHANGELOG.md index a2ac1a4755..b966e4def3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Changelog ========= +## vNext + +### Chores + +- Reorganized tests directories by domain and added flow support to all tests JS files ([PR 1540](https://github.com/input-output-hk/daedalus/pull/1540)) + ## 0.15.0 ### Features diff --git a/features/.eslintrc b/features/.eslintrc deleted file mode 100644 index 0eed3f6a3f..0000000000 --- a/features/.eslintrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "rules": { - "func-names": 0, - "max-len": 0, - "new-cap": [2, { - "capIsNewExceptions": [ - "After", - "AfterAll", - "BeforeAll", - "Before", - "Given", - "When", - "Then" - ] - }] - }, - "globals": { - "daedalus": true - } -} diff --git a/features/README.md b/features/README.md deleted file mode 100644 index 14523556d2..0000000000 --- a/features/README.md +++ /dev/null @@ -1,62 +0,0 @@ -
-Document maintainer: Nikola Glumac
Document status: Active
-
- -# Install Daedalus - -1. Make sure you have node and yarn installed on your machine -2. Clone Daedalus repository to your machine (`git clone git@github.com:input-output-hk/daedalus.git`) -3. Install dependencies from within Daedalus directory: - -```bash -$ yarn install -``` - -# Run unit tests - -Make sure Daedalus is properly installed (see above). - -```bash -$ yarn test:unit -``` - -## Unbound tests - -Unbound tests run as long as you keep them running -(never end except if an error occurs). - -Example: -`yarn test:unit:unbound --tags @mnemonics` -generates and validates mnemonics as long as you keep it -running (the number of executions is updated in the terminal) - -# Run end-to-end tests - -1. Make sure Daedalus is properly installed (see above). -2. Build and run the backend (Cardano SL) following the instructions from [Daedalus](https://github.com/input-output-hk/daedalus/blob/master/README.md#development---with-cardano-wallet) README file. -3. Run Daedalus frontend tests: - -```bash -$ cd daedalus/ -$ yarn nix:dev XXX # XXX = cardano system startup time -$ yarn build -$ yarn test:e2e -``` - -# Run all tests - -```bash -$ yarn test -``` - -Once tests are complete you will get a summary of passed/failed tests in the Terminal window. - -## Keeping Daedalus alive after end-to-end tests - -While working on the tests it's often useful to keep Daedalus alive after the tests have run -(e.g: to inspect the app state). You can pass a special environment var to tell the test script -not to close the app: - -````bash -$ KEEP_APP_AFTER_TESTS=true yarn test:e2e -```` diff --git a/features/tests/e2e/documents/default-wallet.json b/features/tests/e2e/documents/default-wallet.json deleted file mode 100644 index bad1f6d5b4..0000000000 --- a/features/tests/e2e/documents/default-wallet.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "wallet": { - "accounts": [{ "name": "Genesis account", "index": 2147483648 }], - "walletSecretKey": "WIAwbsQgbz9X0WhvOnVeH+yRs7Ri93ESTdMspBHzeLnPUR6hLZL/NazfB40z2x8FZhLwNIt83DCuMR1nGG+ZqvsD/ouyzg3ec729fnrqEMO4A+qPTJmpiRgQZfYO2KDJDRxLtMyofXl90VVZOEke/QddnZ8CGHoR/lCemJgZuvzBpw==", - "walletMeta": { - "name": "Imported Wallet", - "assurance": "normal", - "unit": "ADA" - }, - "passwordHash": "WGQxNHw4fDF8V0NERGRHY0JGcThzelVyeFdza00wM1VjYnloeVBBQXBvdWtwdWFsUTExNGVFdz09fFJXMk5kUmVJYmg2REtsa2lsWG8rQ1lvTStRZmJkMzRmRVd0MG4rSy82YUU9" - }, - "fileType": "WALLETS_EXPORT", - "fileVersion": "1.0.0" -} diff --git a/features/tests/e2e/documents/paper_wallet_certificates/paper-wallet-certificate.pdf b/features/tests/e2e/documents/paper_wallet_certificates/paper-wallet-certificate.pdf deleted file mode 100644 index 64c339b4ba..0000000000 Binary files a/features/tests/e2e/documents/paper_wallet_certificates/paper-wallet-certificate.pdf and /dev/null differ diff --git a/features/tests/e2e/helpers/add-wallet-page-helpers.js b/features/tests/e2e/helpers/add-wallet-page-helpers.js deleted file mode 100644 index 58a956403b..0000000000 --- a/features/tests/e2e/helpers/add-wallet-page-helpers.js +++ /dev/null @@ -1,11 +0,0 @@ -import { waitAndClick } from './shared-helpers'; - -const ADD_WALLET = '.WalletAdd'; -const IMPORT_WALLET_BUTTON = '.importWalletButton'; - -export default { - waitForVisible: (client, { isHidden } = {}) => - client.waitForVisible(ADD_WALLET, null, isHidden), - clickImportButton: client => - waitAndClick(client, `${ADD_WALLET} ${IMPORT_WALLET_BUTTON}`), -}; diff --git a/features/tests/e2e/helpers/app-helpers.js b/features/tests/e2e/helpers/app-helpers.js deleted file mode 100644 index 0adca54379..0000000000 --- a/features/tests/e2e/helpers/app-helpers.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow -import type { WebdriverClient } from '../setup/global-types'; -import { getProcessesByName } from '../../../../source/main/utils/processes'; - -export const waitForDaedalusToExit = async ( - client: WebdriverClient, - timeout: number = 61000 -) => { - const daedalusProcessName = - process.platform === 'linux' ? 'electron' : 'Electron'; - return client.waitUntil( - async () => (await getProcessesByName(daedalusProcessName)).length === 0, - timeout - ); -}; - -export const refreshClient = async (client: WebdriverClient) => { - await client.url(`file://${__dirname}/../../../../dist/renderer/index.html`); -}; diff --git a/features/tests/e2e/helpers/cardano-node-helpers.js b/features/tests/e2e/helpers/cardano-node-helpers.js deleted file mode 100644 index c4075450aa..0000000000 --- a/features/tests/e2e/helpers/cardano-node-helpers.js +++ /dev/null @@ -1,15 +0,0 @@ -// @flow -import type { Daedalus, WebdriverClient } from '../setup/global-types'; -import { getProcessesByName } from '../../../../source/main/utils/processes'; - -declare var daedalus: Daedalus; - -export const getCardanoNodeState = async (client: WebdriverClient) => - (await client.execute(() => daedalus.stores.networkStatus.cardanoNodeState)) - .value; - -export const waitForCardanoNodeToExit = async (client: WebdriverClient) => - client.waitUntil( - async () => (await getProcessesByName('cardano-node')).length === 0, - 61000 - ); diff --git a/features/tests/e2e/helpers/data-layer-migration-helpers.js b/features/tests/e2e/helpers/data-layer-migration-helpers.js deleted file mode 100644 index 0981959da1..0000000000 --- a/features/tests/e2e/helpers/data-layer-migration-helpers.js +++ /dev/null @@ -1,19 +0,0 @@ -const DATA_LAYER_MIGRATION_ACCEPTANCE_COMPONENT = - '.DataLayerMigrationForm_component'; - -const dataLayerMigration = { - waitForVisible: async (client, { isHidden } = {}) => - client.waitForVisible( - DATA_LAYER_MIGRATION_ACCEPTANCE_COMPONENT, - null, - isHidden - ), - acceptMigration: async client => { - await client.execute(() => { - daedalus.actions.profile.acceptDataLayerMigration.trigger(); - }); - await dataLayerMigration.waitForVisible(client, { isHidden: true }); - }, -}; - -export default dataLayerMigration; diff --git a/features/tests/e2e/helpers/dialogs/import-wallet-dialog-helpers.js b/features/tests/e2e/helpers/dialogs/import-wallet-dialog-helpers.js deleted file mode 100644 index f8d9afa72e..0000000000 --- a/features/tests/e2e/helpers/dialogs/import-wallet-dialog-helpers.js +++ /dev/null @@ -1,20 +0,0 @@ -import { expectTextInSelector, waitAndClick } from '../shared-helpers'; - -const IMPORT_WALLET_DIALOG = '.WalletFileImportDialog'; - -export default { - waitForDialog: (client, { isHidden } = {}) => - client.waitForVisible(IMPORT_WALLET_DIALOG, null, isHidden), - selectFile: (client, { filePath }) => - client.chooseFile( - `${IMPORT_WALLET_DIALOG} .FileUploadWidget_dropZone input`, - filePath - ), - clickImport: client => - waitAndClick(client, `${IMPORT_WALLET_DIALOG} .primary`), - expectError: (client, { error }) => - expectTextInSelector(client, { - selector: `${IMPORT_WALLET_DIALOG}_error`, - text: error, - }), -}; diff --git a/features/tests/e2e/helpers/i18n-helpers.js b/features/tests/e2e/helpers/i18n-helpers.js deleted file mode 100644 index d331cf4e17..0000000000 --- a/features/tests/e2e/helpers/i18n-helpers.js +++ /dev/null @@ -1,27 +0,0 @@ -const DEFAULT_LANGUAGE = 'en-US'; - -export default { - formatMessage: async (client, { id, values }) => { - const translation = await client.execute( - (translationId, translationValues) => { - const IntlProvider = require('react-intl').IntlProvider; // eslint-disable-line - const locale = daedalus.stores.profile.currentLocale; - const messages = daedalus.translations; - const intlProvider = new IntlProvider( - { locale, messages: messages[locale] }, - {} - ); - return intlProvider - .getChildContext() - .intl.formatMessage({ id: translationId }, translationValues); - }, - id, - values || {} - ); - return translation.value; - }, - setActiveLanguage: async (client, { language } = {}) => - client.execute(locale => { - daedalus.actions.profile.updateLocale.trigger({ locale }); - }, language || DEFAULT_LANGUAGE), -}; diff --git a/features/tests/e2e/helpers/language-selection-helpers.js b/features/tests/e2e/helpers/language-selection-helpers.js deleted file mode 100644 index bc53148a9d..0000000000 --- a/features/tests/e2e/helpers/language-selection-helpers.js +++ /dev/null @@ -1,14 +0,0 @@ -import i18n from './i18n-helpers'; - -const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; - -const languageSelection = { - waitForVisible: async (client, { isHidden } = {}) => - client.waitForVisible(LANGUAGE_SELECTION_FORM, null, isHidden), - ensureLanguageIsSelected: async (client, { language } = {}) => { - await i18n.setActiveLanguage(client, { language }); - await languageSelection.waitForVisible(client, { isHidden: true }); - }, -}; - -export default languageSelection; diff --git a/features/tests/e2e/helpers/notifications-helpers.js b/features/tests/e2e/helpers/notifications-helpers.js deleted file mode 100644 index 70aea2d1b9..0000000000 --- a/features/tests/e2e/helpers/notifications-helpers.js +++ /dev/null @@ -1,13 +0,0 @@ -import { WalletSyncStateTags } from '../../../../source/renderer/app/domains/Wallet'; - -export const isActiveWalletBeingRestored = async client => { - const result = await client.execute( - expectedSyncTag => - daedalus.stores.wallets.active.syncState.tag === expectedSyncTag, - WalletSyncStateTags.RESTORING - ); - return result.value; -}; - -export const waitForActiveRestoreNotification = (client, { isHidden } = {}) => - client.waitForVisible('.ActiveRestoreNotification', null, isHidden); diff --git a/features/tests/e2e/helpers/route-helpers.js b/features/tests/e2e/helpers/route-helpers.js deleted file mode 100644 index 4da5b90c22..0000000000 --- a/features/tests/e2e/helpers/route-helpers.js +++ /dev/null @@ -1,18 +0,0 @@ -export const getCurrentAppRoute = async function() { - const url = (await this.client.url()).value; - return url.substring(url.indexOf('#/') + 1); // return without the hash -}; - -export const waitUntilUrlEquals = function(expectedUrl) { - const context = this; - return context.client.waitUntil(async () => { - const url = await getCurrentAppRoute.call(context); - return url === expectedUrl; - }); -}; - -export const navigateTo = function(requestedRoute) { - return this.client.execute(route => { - daedalus.actions.router.goToRoute.trigger({ route }); - }, requestedRoute); -}; diff --git a/features/tests/e2e/helpers/screenshot.js b/features/tests/e2e/helpers/screenshot.js deleted file mode 100644 index ab615a1599..0000000000 --- a/features/tests/e2e/helpers/screenshot.js +++ /dev/null @@ -1,25 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { generateFileNameWithTimestamp } from '../../../../source/common/utils/files'; -import ensureDirectoryExists from '../../../../source/main/utils/ensureDirectoryExists'; - -export const generateScreenshotFilePath = prefix => { - const filePath = path.resolve(__dirname, '../screenshots', prefix); - const extension = 'png'; - const fileName = generateFileNameWithTimestamp({ prefix, extension }); - ensureDirectoryExists(filePath); - return `${filePath}/${fileName}`; -}; - -export const getTestNameFromTestFile = testFile => - testFile.replace('features/', '').replace('.feature', ''); - -export const saveScreenshot = async (context, file) => { - await context.browserWindow - .capturePage() - .then(imageBuffer => fs.writeFile(file, imageBuffer)) - .catch(err => { - // eslint-disable-next-line no-console - console.log(err); - }); -}; diff --git a/features/tests/e2e/helpers/shared-helpers.js b/features/tests/e2e/helpers/shared-helpers.js deleted file mode 100644 index 5e4813b619..0000000000 --- a/features/tests/e2e/helpers/shared-helpers.js +++ /dev/null @@ -1,57 +0,0 @@ -import { expect } from 'chai'; - -export const waitAndClick = async (client, selector, ...waitArgs) => { - await client.waitForVisible(selector, ...waitArgs); - await client.waitForEnabled(selector, ...waitArgs); - return client.click(selector); -}; - -export const expectTextInSelector = async (client, { selector, text }) => { - await client.waitForText(selector); - let textOnScreen = await client.getText(selector); - // The selector could exist multiple times in the DOM - if (typeof textOnScreen === 'string') textOnScreen = [textOnScreen]; - // We only compare the first result - expect(textOnScreen[0]).to.equal(text); -}; - -export const waitUntilTextInSelector = async (client, { selector, text }) => - client.waitUntil(async () => { - await client.waitForText(selector); - let textOnScreen = await client.getText(selector); - // The selector could exist multiple times in the DOM - if (typeof textOnScreen === 'string') textOnScreen = [textOnScreen]; - // We only compare the first result - return textOnScreen[0] === text; - }); - -export const getVisibleElementsForSelector = async ( - client, - selectSelector, - waitSelector = selectSelector, - ...waitArgs -) => { - await client.waitForVisible(waitSelector, ...waitArgs); - return client.elements(selectSelector); -}; - -export const getVisibleElementsCountForSelector = async ( - client, - selectSelector, - waitSelector = selectSelector, - ...waitArgs -) => { - const elements = await getVisibleElementsForSelector( - client, - selectSelector, - waitSelector, - ...waitArgs - ); - return elements.value ? elements.value.length : 0; -}; - -export const getVisibleTextsForSelector = async (client, selector) => { - await client.waitForVisible(selector); - const texts = await client.getText(selector); - return [].concat(texts); -}; diff --git a/features/tests/e2e/helpers/sidebar-helpers.js b/features/tests/e2e/helpers/sidebar-helpers.js deleted file mode 100644 index 4981919339..0000000000 --- a/features/tests/e2e/helpers/sidebar-helpers.js +++ /dev/null @@ -1,15 +0,0 @@ -import { waitAndClick } from './shared-helpers'; - -export default { - activateCategory: async (client, { category }) => { - await client.execute(cat => { - daedalus.actions.sidebar.activateSidebarCategory.trigger({ - category: cat, - showSubMenu: true, - }); - }, `/${category}`); - return client.waitForVisible(`.SidebarCategory_active.${category}`); - }, - clickAddWalletButton: client => - waitAndClick(client, '.SidebarWalletsMenu_addWalletButton'), -}; diff --git a/features/tests/e2e/helpers/terms-of-use-helpers.js b/features/tests/e2e/helpers/terms-of-use-helpers.js deleted file mode 100644 index e387b7c923..0000000000 --- a/features/tests/e2e/helpers/terms-of-use-helpers.js +++ /dev/null @@ -1,14 +0,0 @@ -const TERMS_OF_USE_FORM = '.TermsOfUseForm_component'; - -const termsOfUse = { - waitForVisible: async (client, { isHidden } = {}) => - client.waitForVisible(TERMS_OF_USE_FORM, null, isHidden), - acceptTerms: async client => { - await client.execute(() => { - daedalus.actions.profile.acceptTermsOfUse.trigger(); - }); - await termsOfUse.waitForVisible(client, { isHidden: true }); - }, -}; - -export default termsOfUse; diff --git a/features/tests/e2e/setup/global-types.js b/features/tests/e2e/setup/global-types.js deleted file mode 100644 index e573d4a542..0000000000 --- a/features/tests/e2e/setup/global-types.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import type { Api } from '../../../../source/renderer/app/api'; -import type { ActionsMap } from '../../../../source/renderer/app/actions'; -import type { StoresMap } from '../../../../source/renderer/app/stores'; - -export type Daedalus = { - api: Api, - environment: Object, - actions: ActionsMap, - stores: StoresMap, - translations: Object, - reset: Function, -}; - -export type WebdriverExecuteResult = { value: T }; - -export type WebdriverClient = { - execute: (script: Function) => WebdriverExecuteResult, - waitUntil: (script: Function, timeout?: number) => Promise, - url: (url: string) => Promise, -}; diff --git a/features/tests/e2e/setup/i18n.js b/features/tests/e2e/setup/i18n.js deleted file mode 100644 index f9afc7b852..0000000000 --- a/features/tests/e2e/setup/i18n.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Before } from 'cucumber'; - -Before(function() { - this.intl = async (translationId, translationValues = {}) => { - const translation = await this.client.execute( - (id, values) => { - const IntlProvider = require('react-intl').IntlProvider; // eslint-disable-line - const locale = daedalus.stores.profile.currentLocale; - const messages = daedalus.translations; - const intlProvider = new IntlProvider( - { locale, messages: messages[locale] }, - {} - ); - return intlProvider - .getChildContext() - .intl.formatMessage({ id }, values); - }, - translationId, - translationValues - ); - return translation.value; - }; -}); diff --git a/features/tests/e2e/setup/webdriver.js b/features/tests/e2e/setup/webdriver.js deleted file mode 100644 index 33d9b85225..0000000000 --- a/features/tests/e2e/setup/webdriver.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Before } from 'cucumber'; - -Before(function() { - this.waitAndClick = async (selector, ...waitArgs) => { - await this.client.waitForVisible(selector, ...waitArgs); - return this.client.click(selector); - }; -}); diff --git a/features/tests/e2e/steps/helper-steps.js b/features/tests/e2e/steps/helper-steps.js deleted file mode 100644 index d0141ace1a..0000000000 --- a/features/tests/e2e/steps/helper-steps.js +++ /dev/null @@ -1,51 +0,0 @@ -import { When } from 'cucumber'; -import { - generateScreenshotFilePath, - saveScreenshot, -} from '../helpers/screenshot'; - -const oneHour = 60 * 60 * 1000; -// Helper step to pause execution for up to an hour ;) -When(/^I freeze$/, { timeout: oneHour }, callback => { - setTimeout(callback, oneHour); -}); - -When(/^I take a screenshot named "([^"]*)"$/, async function(testName) { - const file = generateScreenshotFilePath(testName); - await saveScreenshot(this, file); -}); - -When(/^I inject fault named "([^"]*)"$/, async function(faultName) { - await this.client.executeAsync((name, done) => { - daedalus.api.ada - .setCardanoNodeFault([name, true]) - .then(done) - .catch(e => { - throw e; - }); - }, faultName); -}); - -When(/^I trigger the apply-update endpoint$/, async function() { - await this.client.executeAsync(done => { - daedalus.api.ada - .applyUpdate() - .then(done) - .catch(e => { - throw e; - }); - }); -}); - -When(/^I set next update version to "([^"]*)"$/, async function( - applicationVersion -) { - await this.client.executeAsync((version, done) => { - daedalus.api.ada - .setNextUpdate(parseInt(version, 10)) - .then(done) - .catch(e => { - throw e; - }); - }, applicationVersion); -}); diff --git a/features/tests/e2e/steps/setup-steps.js b/features/tests/e2e/steps/setup-steps.js deleted file mode 100644 index 370a5ebccc..0000000000 --- a/features/tests/e2e/steps/setup-steps.js +++ /dev/null @@ -1,12 +0,0 @@ -import { Given } from 'cucumber'; -import termsOfUse from '../helpers/terms-of-use-helpers'; -import languageSelection from '../helpers/language-selection-helpers'; -import dataLayerMigration from '../helpers/data-layer-migration-helpers'; - -Given(/^I have completed the basic setup$/, async function() { - await languageSelection.ensureLanguageIsSelected(this.client, { - language: 'en-US', - }); - await termsOfUse.acceptTerms(this.client); - await dataLayerMigration.acceptMigration(this.client); -}); diff --git a/features/tests/unit/setup/context.js b/features/tests/unit/setup/context.js deleted file mode 100644 index 8fa1261dbe..0000000000 --- a/features/tests/unit/setup/context.js +++ /dev/null @@ -1,5 +0,0 @@ -import { Before } from 'cucumber'; - -Before(function() { - this.context = {}; -}); diff --git a/features/tests/unit/setup/parameter-types.js b/features/tests/unit/setup/parameter-types.js deleted file mode 100644 index 9aad9bd47f..0000000000 --- a/features/tests/unit/setup/parameter-types.js +++ /dev/null @@ -1,8 +0,0 @@ -import { defineParameterType } from 'cucumber'; - -// Add {bool} parameter type -defineParameterType({ - name: 'bool', - regexp: /true|false/, - transformer: b => b === 'true', -}); diff --git a/gulpfile.js b/gulpfile.js index 3ceec80513..53a480bd9f 100755 --- a/gulpfile.js +++ b/gulpfile.js @@ -182,7 +182,7 @@ gulp.task('build:themes', gulp.series('clean:dist', 'prepare:themes')); gulp.task( 'test:e2e:nodemon', shell.task( - 'nodemon --watch dist --watch features --exec "yarn test:e2e --tags \'@e2e and @watch\'"' + 'nodemon --watch dist --watch tests --exec "yarn test:e2e --tags \'@e2e and @watch\'"' ) ); diff --git a/package.json b/package.json index b389074c01..3dab01472b 100644 --- a/package.json +++ b/package.json @@ -10,18 +10,18 @@ "start:dev": "NODE_ENV=development gulp start", "dev": "IS_WATCH_MODE=true gulp dev", "test": "NODE_ENV=test yarn build && yarn test:unit && yarn test:e2e", - "test:unit": "yarn cucumber --require 'features/tests/unit/**/*.js' --tags '@unit and not @skip and not @wip'", - "test:unit:watch": "nodemon --watch source --watch features --exec \"yarn test:unit --tags '@unit and @watch'\"", - "test:unit:unbound": "yarn cucumber --require 'features/tests/unit/**/*.js' --tags '@unbound and not @skip and not @wip'", - "test:e2e": "yarn cucumber --require 'features/tests/e2e/**/*.js' --tags '@e2e and not @skip and not @wip'", + "test:unit": "yarn cucumber --require 'tests/**/unit/*.js' --tags '@unit and not @skip and not @wip'", + "test:unit:watch": "nodemon --watch source --watch tests --exec \"yarn test:unit --tags '@unit and @watch'\"", + "test:unit:unbound": "yarn cucumber --require 'tests/**/unit/*.js' --tags '@unbound and not @skip and not @wip'", + "test:e2e": "yarn cucumber --require 'tests/**/e2e/*.js' --tags '@e2e and not @skip and not @wip'", "test:e2e:watch": "gulp test:e2e:watch", + "cucumber": "cross-env NODE_ENV=test cucumber-js tests --require 'tests/**/*.js' --require-module @babel/register -f node_modules/cucumber-pretty --format-options '{\"snippetInterface\": \"async-await\"}'", "test:e2e:watch:once": "KEEP_APP_AFTER_TESTS=true yarn test:e2e --tags '@e2e and @watch'", - "cucumber": "cross-env NODE_ENV=test cucumber-js --require-module @babel/register -f node_modules/cucumber-pretty --format-options '{\"snippetInterface\": \"async-await\"}'", "debug": "gulp debug", "package": "gulp build && cross-env NODE_ENV=production node -r @babel/register -r @babel/polyfill scripts/package.js", "package:all": "yarn package --all", "cleanup": "mop -v", - "lint": "eslint --format=node_modules/eslint-formatter-pretty source features storybook *.js", + "lint": "eslint --format=node_modules/eslint-formatter-pretty source tests storybook *.js", "flow:test": "flow; test $? -eq 0 -o $? -eq 2", "prettier": "./node_modules/.bin/prettier \"**/*.*\"", "prettier:check": "yarn prettier --check", diff --git a/scripts/package.js b/scripts/package.js index 4ce3fe1d2e..c9956287dd 100755 --- a/scripts/package.js +++ b/scripts/package.js @@ -27,7 +27,7 @@ const DEFAULT_OPTS = { ignore: [ /^\/.buildkite($|\/)/, /^\/.storybook($|\/)/, - /^\/features($|\/)/, + /^\/tests($|\/)/, /^\/flow($|\/)/, /^\/node_modules($|\/)/, /^\/scripts($|\/)/, diff --git a/source/renderer/app/api/index.js b/source/renderer/app/api/index.js index 7ac262bcb4..ab532ed216 100644 --- a/source/renderer/app/api/index.js +++ b/source/renderer/app/api/index.js @@ -5,6 +5,7 @@ import LocalStorageApi from './utils/localStorage'; export type Api = { ada: AdaApi, localStorage: LocalStorageApi, + setFaultyNodeSettingsApi?: boolean, }; export const setupApi = (isTest: boolean, network: string): Api => ({ diff --git a/tests/.eslintrc b/tests/.eslintrc new file mode 100644 index 0000000000..4e788345ec --- /dev/null +++ b/tests/.eslintrc @@ -0,0 +1,20 @@ +{ + "rules": { + "func-names": 0, + "max-len": 0, + "new-cap": [2, { + "capIsNewExceptions": [ + "After", + "AfterAll", + "BeforeAll", + "Before", + "Given", + "When", + "Then" + ] + }] + }, + "globals": { + "daedalus": true + } +} diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000..45ef7c502e --- /dev/null +++ b/tests/README.md @@ -0,0 +1,78 @@ +
+Document maintainer: Nikola Glumac, Marcus Hurney
Document status: Active
+
+ +# Daedalus Acceptance Tests Overview + +### File types and Cucumber syntax + +- Cucumber looks for files with a `.feature` file extension as the starting point for executing `scenarios` defined within a given `.feature` file. Each `scenario` contains one or more `steps`. `scenarios` describe the broader context or purpose of a given set of steps while the `steps` themselves describe the intended functionality of the Javascript test executables. Together, the collective `scenarios` within a `.feature` file comprise test coverage of at least one feature within the Daedalus UI. + +### JavaScript Executables + +- Each `step` in a `.feature` file will match a JavaScript `string` passed as the first parameter to a `step-definition` function within a separate `.js` file. A `step-definition` also contains the executable JavaScript function(s) that run the test logic itself. The `step`'s name written as text within a `.feature` file must exactly match the associated JavaScript `string` within a `step-definition` in order for the executable to run. + +### File Structure + +- All the files comprising the Daedalus acceptance tests are divided into directories by domain. A test belongs to a domain depending on the category of functionality it's meant to test. Within Daedalus the domains `wallets`, `paper-wallets`, `addresses`, `transactions`, `navigation`, `nodes`, `settings`, and `common`. These domains also constitute the top level directories of the Daedalus acceptance tests. + +# Running Daedalus Acceptance Tests + +### Install Daedalus + +1. Make sure you have node and yarn installed on your machine +2. Clone Daedalus repository to your machine (`git clone git@github.com:input-output-hk/daedalus.git`) +3. Install dependencies from within Daedalus directory: + +```bash +$ yarn install +``` + +### Run unit tests + +Make sure Daedalus is properly installed (see above). + +```bash +$ yarn test:unit +``` + +### Unbound tests + +Unbound tests run as long as you keep them running +(never end except if an error occurs). + +Example: +`yarn test:unit:unbound --tags @mnemonics` +generates and validates mnemonics as long as you keep it +running (the number of executions is updated in the terminal) + +### Run end-to-end tests + +1. Make sure Daedalus is properly installed (see above). +2. Build and run the backend (Cardano SL) following the instructions from [Daedalus](https://github.com/input-output-hk/daedalus/blob/master/README.md#development---with-cardano-wallet) README file. +3. Run Daedalus frontend tests: + +```bash +$ cd daedalus/ +$ yarn nix:dev XXX # XXX = cardano system startup time +$ yarn build +$ yarn test:e2e +``` + +### Run all tests + +```bash +$ yarn test +``` + +Once tests are complete you will get a summary of passed/failed tests in the Terminal window. + +### Keeping Daedalus alive after end-to-end tests + +While working on the tests it's often useful to keep Daedalus alive after the tests have run +(e.g: to inspect the app state). You can pass a special environment var to tell the test script +not to close the app: + +````bash +$ KEEP_APP_AFTER_TESTS=true yarn test:e2e +```` \ No newline at end of file diff --git a/features/receive-money.feature b/tests/addresses/e2e/features/receive-money.feature similarity index 100% rename from features/receive-money.feature rename to tests/addresses/e2e/features/receive-money.feature diff --git a/features/wallet-address-generate.feature b/tests/addresses/e2e/features/wallet-address-generate.feature similarity index 100% rename from features/wallet-address-generate.feature rename to tests/addresses/e2e/features/wallet-address-generate.feature diff --git a/features/tests/e2e/steps/receive-steps.js b/tests/addresses/e2e/steps/addresses.js similarity index 58% rename from features/tests/e2e/steps/receive-steps.js rename to tests/addresses/e2e/steps/addresses.js index 51d1c15f9c..1b28dd45ea 100644 --- a/features/tests/e2e/steps/receive-steps.js +++ b/tests/addresses/e2e/steps/addresses.js @@ -1,21 +1,32 @@ -import { Given, When, Then } from 'cucumber'; +// @flow +import { Given, Then, When } from 'cucumber'; import { expect } from 'chai'; import { - waitAndClick, getVisibleElementsCountForSelector, -} from '../helpers/shared-helpers'; + waitAndClick, +} from '../../../common/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; +const SELECTORS = { + ADDRESS_ACTIVE: '.WalletReceive_hash', + ADDRESS_COMPONENT: '.Address_component', + ADDRESS_USED: '.Address_usedWalletAddress', + GENERATE_ADDRESS_BTN: '.generateAddressButton:not(.WalletReceive_spinning)', + SHOW_USED_SWITCH: '.SimpleSwitch_switch', +}; Given('I generate {int} addresses', async function(numberOfAddresses) { for (let i = 0; i < numberOfAddresses; i++) { await waitAndClick( this.client, - '.generateAddressButton:not(.WalletReceive_spinning)' + SELECTORS.GENERATE_ADDRESS_BTN ); } }); When('I click the ShowUsed switch', async function() { - await waitAndClick(this.client, '.SimpleSwitch_switch'); + await waitAndClick(this.client, SELECTORS.SHOW_USED_SWITCH); }); Then('I should see {int} used addresses', { timeout: 60000 }, async function( @@ -23,8 +34,8 @@ Then('I should see {int} used addresses', { timeout: 60000 }, async function( ) { const addressesFound = await getVisibleElementsCountForSelector( this.client, - '.Address_usedWalletAddress', - '.Address_usedWalletAddress', + SELECTORS.ADDRESS_USED, + SELECTORS.ADDRESS_USED, 60000 ); expect(addressesFound).to.equal(numberOfAddresses); @@ -33,7 +44,7 @@ Then('I should see {int} used addresses', { timeout: 60000 }, async function( Then('I should see {int} addresses', async function(numberOfAddresses) { const addressesFound = await getVisibleElementsCountForSelector( this.client, - '.Address_component' + SELECTORS.ADDRESS_COMPONENT ); expect(addressesFound).to.equal(numberOfAddresses); }); @@ -42,12 +53,14 @@ Then('I should see the following addresses:', async function(table) { const expectedAdresses = table.hashes(); let addresses; await this.client.waitUntil(async () => { - addresses = await this.client.getAttribute('.Address_component', 'class'); + addresses = await this.client.getAttribute(SELECTORS.ADDRESS_COMPONENT, 'class'); return addresses.length === expectedAdresses.length; }); - addresses.forEach((address, index) => - expect(address).to.include(expectedAdresses[index].ClassName) - ); + if (addresses) { + addresses.forEach((address, index) => + expect(address).to.include(expectedAdresses[index].ClassName) + ); + } }); Then('The active address should be the newest one', async function() { @@ -56,6 +69,6 @@ Then('The active address should be the newest one', async function() { } = await this.client.execute( () => daedalus.stores.addresses.lastGeneratedAddress ); - const activeAddress = await this.client.getText('.WalletReceive_hash'); + const activeAddress = await this.client.getText(SELECTORS.ADDRESS_ACTIVE); expect(lastGeneratedAddress).to.equal(activeAddress); }); diff --git a/tests/common/e2e/steps/helpers.js b/tests/common/e2e/steps/helpers.js new file mode 100644 index 0000000000..4e7afd06ff --- /dev/null +++ b/tests/common/e2e/steps/helpers.js @@ -0,0 +1,100 @@ +// @flow +import fs from 'fs'; +import path from 'path'; +import { expect } from 'chai'; +import { generateFileNameWithTimestamp } from '../../../../source/common/utils/files'; +import ensureDirectoryExists from '../../../../source/main/utils/ensureDirectoryExists'; +import type { WebdriverClient } from '../../../types'; + +export const expectTextInSelector = async ( + client: WebdriverClient, + { selector, text }: { selector: string, text: string } +) => { + await client.waitForText(selector); + let textOnScreen = await client.getText(selector); + // The selector could exist multiple times in the DOM + if (typeof textOnScreen === 'string') textOnScreen = [textOnScreen]; + // We only compare the first result + expect(textOnScreen[0]).to.equal(text); +}; + +export const generateScreenshotFilePath = (prefix: string) => { + const prefixParts = prefix.split('/'); + const testName = prefixParts.pop(); + const testPath = prefixParts.slice(1).join('/'); + const filePath = path.resolve(__dirname, '../../../screenshots/', testPath); + const extension = 'png'; + const fileName = generateFileNameWithTimestamp({ prefix: testName, extension }); + ensureDirectoryExists(filePath); + return `${filePath}/${fileName}`; +}; + +export const getTestNameFromTestFile = (testFile: string) => testFile.split('.feature').join(''); + +export const getVisibleElementsCountForSelector = async ( + client: WebdriverClient, + selectSelector: string, + waitSelector: string = selectSelector, + ...waitArgs: Array<*> +) => { + const elements = await getVisibleElementsForSelector( + client, + selectSelector, + waitSelector, + ...waitArgs + ); + return elements.value ? elements.value.length : 0; +}; + +export const getVisibleElementsForSelector = async ( + client: WebdriverClient, + selectSelector: string, + waitSelector: string = selectSelector, + ...waitArgs: Array<*> +) => { + await client.waitForVisible(waitSelector, ...waitArgs); + return client.elements(selectSelector); +}; + +export const getVisibleTextsForSelector = async ( + client: WebdriverClient, + selector: string +): Promise> => { + await client.waitForVisible(selector); + const texts = await client.getText(selector); + return [].concat(texts); +}; + +export const saveScreenshot = async ( + context: Object, + file: any +) => await context.browserWindow + .capturePage() + .then(imageBuffer => fs.writeFile(file, imageBuffer)) + .catch(err => { + // eslint-disable-next-line no-console + console.log(err); + }); + +export const waitAndClick = async ( + client: WebdriverClient, + selector: string, + ...waitArgs: Array<*> +) => { + await client.waitForVisible(selector, ...waitArgs); + await client.waitForEnabled(selector, ...waitArgs); + return client.click(selector); +}; + +export const waitUntilTextInSelector = async ( + client: WebdriverClient, + { selector, text }: { selector: string, text: string } +) => + client.waitUntil(async () => { + await client.waitForText(selector); + let textOnScreen = await client.getText(selector); + // The selector could exist multiple times in the DOM + if (typeof textOnScreen === 'string') textOnScreen = [textOnScreen]; + // We only compare the first result + return textOnScreen[0] === text; + }); diff --git a/features/navigate-general-settings-menu.feature b/tests/navigation/e2e/features/navigate-general-settings-menu.feature similarity index 100% rename from features/navigate-general-settings-menu.feature rename to tests/navigation/e2e/features/navigate-general-settings-menu.feature diff --git a/features/navigate-sidebar-categories.feature b/tests/navigation/e2e/features/navigate-sidebar-categories.feature similarity index 100% rename from features/navigate-sidebar-categories.feature rename to tests/navigation/e2e/features/navigate-sidebar-categories.feature diff --git a/features/navigate-wallet-tabs.feature b/tests/navigation/e2e/features/navigate-wallet-tabs.feature similarity index 100% rename from features/navigate-wallet-tabs.feature rename to tests/navigation/e2e/features/navigate-wallet-tabs.feature diff --git a/features/switching-between-wallets.feature b/tests/navigation/e2e/features/switching-between-wallets.feature similarity index 100% rename from features/switching-between-wallets.feature rename to tests/navigation/e2e/features/switching-between-wallets.feature diff --git a/features/toggle-sidebar-submenus.feature b/tests/navigation/e2e/features/toggle-sidebar-submenus.feature similarity index 100% rename from features/toggle-sidebar-submenus.feature rename to tests/navigation/e2e/features/toggle-sidebar-submenus.feature diff --git a/tests/navigation/e2e/steps/general-settings.js b/tests/navigation/e2e/steps/general-settings.js new file mode 100644 index 0000000000..e7de37b64f --- /dev/null +++ b/tests/navigation/e2e/steps/general-settings.js @@ -0,0 +1,15 @@ +// @flow +import { Given, Then } from 'cucumber'; +import { navigateTo, waitUntilUrlEquals } from './helpers'; + +Given(/^I am on the General Settings "([^"]*)" screen$/, async function( + screen +) { + await navigateTo.call(this, `/settings/${screen}`); +}); + +Then(/^I should see General Settings "([^"]*)" screen$/, async function( + screenName +) { + return waitUntilUrlEquals.call(this, `/settings/${screenName}`); +}); \ No newline at end of file diff --git a/tests/navigation/e2e/steps/helpers.js b/tests/navigation/e2e/steps/helpers.js new file mode 100644 index 0000000000..bd9861f95f --- /dev/null +++ b/tests/navigation/e2e/steps/helpers.js @@ -0,0 +1,45 @@ +// @flow +import { waitAndClick } from '../../../common/e2e/steps/helpers'; +import type { Daedalus, WebdriverClient } from '../../../types'; + +declare var daedalus: Daedalus; +const SELECTORS = { + ACTIVE_CATEGORY: '.SidebarCategory_active', + ADD_WALLET_BTN: '.SidebarWalletsMenu_addWalletButton', +}; + +export const getCurrentAppRoute = async function() { + const url = (await this.client.url()).value; + return url.substring(url.indexOf('#/') + 1); // return without the hash +}; + +export const waitUntilUrlEquals = function(expectedUrl: string) { + const context = this; + return context.client.waitUntil(async () => { + const url = await getCurrentAppRoute.call(context); + return url === expectedUrl; + }); +}; + +export const navigateTo = function(requestedRoute: string) { + return this.client.execute(route => { + daedalus.actions.router.goToRoute.trigger({ route }); + }, requestedRoute); +}; + +export const sidebarHelpers = { + activateCategory: async ( + client: WebdriverClient, + { category }: { category: string } + ) => { + await client.execute(cat => { + daedalus.actions.sidebar.activateSidebarCategory.trigger({ + category: cat, + showSubMenu: true, + }); + }, `/${category}`); + return client.waitForVisible(`${SELECTORS.ACTIVE_CATEGORY}.${category}`); + }, + clickAddWalletButton: (client: WebdriverClient) => + waitAndClick(client, SELECTORS.ADD_WALLET_BTN), +}; \ No newline at end of file diff --git a/features/tests/e2e/steps/sidebar-steps.js b/tests/navigation/e2e/steps/sidebar.js similarity index 54% rename from features/tests/e2e/steps/sidebar-steps.js rename to tests/navigation/e2e/steps/sidebar.js index 82f3ad22fd..177fe8c21b 100644 --- a/features/tests/e2e/steps/sidebar-steps.js +++ b/tests/navigation/e2e/steps/sidebar.js @@ -1,10 +1,24 @@ +// @flow import { Given, When, Then } from 'cucumber'; -import sidebar from '../helpers/sidebar-helpers'; +import { sidebarHelpers } from './helpers'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; +const SELECTORS = { + CATEGORY_ACTIVE: '.SidebarCategory_active', + CATEGORY_COMPONENT: '.SidebarCategory_component', + LAYOUT_COMPONENT: '.SidebarLayout_component', + MENU_COMPONENT: '.SidebarMenu_component', + MENU_VISIBLE: '.SidebarMenu_visible', + SIDEBAR_COMPONENT: '.Sidebar_component', + TOP_BAR: '.SidebarLayout_topbar', + TOP_BAR_LEFT_ICON: '.TopBar_leftIcon', +}; Given(/^the sidebar submenu is (hidden|visible)/, async function(state) { const isVisible = state === 'visible'; - await this.client.waitForVisible('.Sidebar_component'); - await this.client.executeAsync((visible, done) => { + await this.client.waitForVisible(SELECTORS.SIDEBAR_COMPONENT); + await this.client.executeAsync((visible, SELECTORS, done) => { const { isShowingSubMenus } = daedalus.stores.sidebar; let sidebarWillAnimate = false; if (isShowingSubMenus !== visible) { @@ -13,7 +27,7 @@ Given(/^the sidebar submenu is (hidden|visible)/, async function(state) { } if (sidebarWillAnimate) { // Wait until the sidebar transition is finished -> otherwise webdriver click error! - const sidebarElement = document.querySelectorAll('.Sidebar_component')[0]; + const sidebarElement = document.querySelectorAll(SELECTORS.SIDEBAR_COMPONENT)[0]; const onTransitionFinished = () => { sidebarElement.removeEventListener( 'transitioned', @@ -25,24 +39,24 @@ Given(/^the sidebar submenu is (hidden|visible)/, async function(state) { } else { done(); } - }, isVisible); - return this.client.waitForExist('.SidebarMenu_visible', null, !isVisible); + }, isVisible, SELECTORS); + return this.client.waitForExist(SELECTORS.MENU_VISIBLE, null, !isVisible); }); Given(/^The sidebar shows the "([^"]*)" category$/, function(category) { - return sidebar.activateCategory(this.client, { category }); + return sidebarHelpers.activateCategory(this.client, { category }); }); When(/^I click on the sidebar toggle button$/, function() { - return this.waitAndClick('.SidebarLayout_topbar .TopBar_leftIcon'); + return this.waitAndClick(`${SELECTORS.TOP_BAR} ${SELECTORS.TOP_BAR_LEFT_ICON}`); }); When(/^I click on the "([^"]*)" category in the sidebar$/, function(category) { - return this.waitAndClick(`.SidebarCategory_component.${category}`); + return this.waitAndClick(`${SELECTORS.CATEGORY_COMPONENT}.${category}`); }); When(/^I click on the add wallet button in the sidebar$/, function() { - return sidebar.clickAddWalletButton(this.client); + return sidebarHelpers.clickAddWalletButton(this.client); }); When(/^I click on the "([^"]*)" wallet in the sidebar$/, function(walletName) { @@ -54,12 +68,16 @@ When(/^I click on the "([^"]*)" wallet in the sidebar$/, function(walletName) { Then(/^the sidebar submenu should be (hidden|visible)/, function(state) { const waitForHidden = state === 'hidden'; return this.client.waitForVisible( - '.SidebarMenu_component', + SELECTORS.MENU_COMPONENT, null, waitForHidden ); }); Then(/^The "([^"]*)" category should be active$/, function(category) { - return this.client.waitForVisible(`.SidebarCategory_active.${category}`); + return this.client.waitForVisible(`${SELECTORS.CATEGORY_ACTIVE}.${category}`); +}); + +Then(/^I should see the initial screen$/, function() { + return this.client.waitForVisible(SELECTORS.LAYOUT_COMPONENT); }); diff --git a/features/tests/e2e/documents/dummy-news.json b/tests/news/e2e/documents/dummy-news.json similarity index 100% rename from features/tests/e2e/documents/dummy-news.json rename to tests/news/e2e/documents/dummy-news.json diff --git a/features/newsfeed.feature b/tests/news/e2e/features/newsfeed.feature similarity index 100% rename from features/newsfeed.feature rename to tests/news/e2e/features/newsfeed.feature diff --git a/features/tests/e2e/steps/newsfeed-steps.js b/tests/news/e2e/steps/newsfeed-steps.js similarity index 95% rename from features/tests/e2e/steps/newsfeed-steps.js rename to tests/news/e2e/steps/newsfeed-steps.js index 6385d25d58..eda98ba111 100644 --- a/features/tests/e2e/steps/newsfeed-steps.js +++ b/tests/news/e2e/steps/newsfeed-steps.js @@ -7,7 +7,7 @@ import newsDummyJson from '../documents/dummy-news.json'; import { expectTextInSelector, getVisibleElementsCountForSelector, -} from '../helpers/shared-helpers'; +} from '../../../common/e2e/steps/helpers'; async function prepareFakeNews(context, fakeNews, preparation, ...args) { // Run custom preparation logic @@ -64,7 +64,8 @@ async function prepareNewsOfType( ); } -function setNewsFeedIsOpen(client, flag) { +// Set newsfeed to open before each newsfeed step +export function setNewsFeedIsOpen(client, flag) { return client.execute(desiredState => { if (daedalus.stores.app.newsFeedIsOpen !== desiredState) { daedalus.actions.app.toggleNewsFeed.trigger(); @@ -72,8 +73,8 @@ function setNewsFeedIsOpen(client, flag) { }, flag); } -// Reset the fake news -function resetTestNews(client) { +// Reset the fake news before each newsfeed step +export function resetTestNews(client) { return client.executeAsync(done => { daedalus.api.ada.setFakeNewsFeedJsonForTesting({ updatedAt: Date.now(), @@ -83,13 +84,6 @@ function resetTestNews(client) { }); } -// SCENARIO HOOKS - -Before({ tags: '@newsfeed' }, async function() { - setNewsFeedIsOpen(this.client, false); - resetTestNews(this.client); -}); - // GIVEN STEPS Given(/^there (?:are|is)\s?(\d+)? (read|unread) (\w+?)s?$/, async function( @@ -142,6 +136,8 @@ Given('the latest alert will cover the screen', async function() { }); }); +// WHEN STEPS + When('I click on the newsfeed icon', async function() { await this.waitAndClick('.NewsFeedIcon_component'); }); @@ -164,6 +160,8 @@ When('I click on the alert in the newsfeed', async function() { await this.waitAndClick('.NewsItem_alert.NewsItem_isRead'); }); +// THEN STEPS + Then('i should see the newsfeed icon', async function() { await this.client.waitForVisible('.NewsFeedIcon_component'); }); diff --git a/features/about-dialog.feature b/tests/nodes/e2e/features/about-dialog.feature similarity index 100% rename from features/about-dialog.feature rename to tests/nodes/e2e/features/about-dialog.feature diff --git a/features/app-version-difference.feature b/tests/nodes/e2e/features/app-version-difference.feature similarity index 100% rename from features/app-version-difference.feature rename to tests/nodes/e2e/features/app-version-difference.feature diff --git a/features/block-consolidation-page.feature b/tests/nodes/e2e/features/block-consolidation-page.feature similarity index 100% rename from features/block-consolidation-page.feature rename to tests/nodes/e2e/features/block-consolidation-page.feature diff --git a/features/data-layer-migration.feature b/tests/nodes/e2e/features/data-layer-migration.feature similarity index 100% rename from features/data-layer-migration.feature rename to tests/nodes/e2e/features/data-layer-migration.feature diff --git a/features/local-time-difference.feature b/tests/nodes/e2e/features/local-time-difference.feature similarity index 100% rename from features/local-time-difference.feature rename to tests/nodes/e2e/features/local-time-difference.feature diff --git a/features/no-disk-space.feature b/tests/nodes/e2e/features/no-disk-space.feature similarity index 100% rename from features/no-disk-space.feature rename to tests/nodes/e2e/features/no-disk-space.feature diff --git a/features/node-update-exit.feature b/tests/nodes/e2e/features/node-update-exit.feature similarity index 100% rename from features/node-update-exit.feature rename to tests/nodes/e2e/features/node-update-exit.feature diff --git a/features/node-update-notification.feature b/tests/nodes/e2e/features/node-update-notification.feature similarity index 100% rename from features/node-update-notification.feature rename to tests/nodes/e2e/features/node-update-notification.feature diff --git a/features/quit-app.feature b/tests/nodes/e2e/features/quit-app.feature similarity index 100% rename from features/quit-app.feature rename to tests/nodes/e2e/features/quit-app.feature diff --git a/features/trouble-connecting-notification.feature b/tests/nodes/e2e/features/trouble-connecting-notification.feature similarity index 100% rename from features/trouble-connecting-notification.feature rename to tests/nodes/e2e/features/trouble-connecting-notification.feature diff --git a/features/trouble-syncing-notification.feature b/tests/nodes/e2e/features/trouble-syncing-notification.feature similarity index 100% rename from features/trouble-syncing-notification.feature rename to tests/nodes/e2e/features/trouble-syncing-notification.feature diff --git a/features/wallet-settings-recovery-phrase-verification.feature b/tests/nodes/e2e/features/wallet-settings-recovery-phrase-verification.feature similarity index 99% rename from features/wallet-settings-recovery-phrase-verification.feature rename to tests/nodes/e2e/features/wallet-settings-recovery-phrase-verification.feature index e327ad01bf..d46f309074 100644 --- a/features/wallet-settings-recovery-phrase-verification.feature +++ b/tests/nodes/e2e/features/wallet-settings-recovery-phrase-verification.feature @@ -1,4 +1,4 @@ -@e2e @watch +@e2e Feature: Wallet Settings - Recovery Phrase Verification Background: diff --git a/features/tests/e2e/steps/about-dialog.js b/tests/nodes/e2e/steps/about-dialog.js similarity index 68% rename from features/tests/e2e/steps/about-dialog.js rename to tests/nodes/e2e/steps/about-dialog.js index a356577a5c..2c5d8097fc 100644 --- a/features/tests/e2e/steps/about-dialog.js +++ b/tests/nodes/e2e/steps/about-dialog.js @@ -1,6 +1,14 @@ +// @flow import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; import packageJson from '../../../../package.json'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; +const SELECTORS = { + CONTAINER: '.About_container', + VERSION: '.About_daedalusVersion', +}; Given(/^I open the About dialog$/, async function() { this.client.execute(() => daedalus.actions.app.openAboutDialog.trigger()); @@ -12,14 +20,14 @@ When(/^I close the About dialog$/, function() { Then(/^the About dialog is (hidden|visible)/, async function(state) { const isVisible = state === 'visible'; - return this.client.waitForVisible('.About_container', null, !isVisible); + return this.client.waitForVisible(SELECTORS.CONTAINER, null, !isVisible); }); Then( /^the About dialog and package.json show the same Daedalus version/, async function() { const { version: packageJsonVersion } = packageJson; - const aboutVersion = await this.client.getText('.About_daedalusVersion'); + const aboutVersion = await this.client.getText(SELECTORS.VERSION); expect(aboutVersion).to.equal(packageJsonVersion); } ); diff --git a/features/tests/e2e/steps/app-version-difference.js b/tests/nodes/e2e/steps/app-version-difference.js similarity index 61% rename from features/tests/e2e/steps/app-version-difference.js rename to tests/nodes/e2e/steps/app-version-difference.js index 2c7e789bbb..c9567fc35e 100644 --- a/features/tests/e2e/steps/app-version-difference.js +++ b/tests/nodes/e2e/steps/app-version-difference.js @@ -1,21 +1,25 @@ +// @flow import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; import { environment } from '../../../../source/main/environment'; -import { getVisibleTextsForSelector } from '../helpers/shared-helpers'; -import i18n from '../helpers/i18n-helpers'; - -const SELECTORS = { - MANUAL_UPDATE_VERSION_INFO: - '.ManualUpdate_content .ManualUpdate_description p:nth-child(2)', -}; +import { getVisibleTextsForSelector } from '../../../common/e2e/steps/helpers'; +import { i18nHelpers } from '../../../settings/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; +declare var daedalus: Daedalus; const currentAppVersion = environment.version; const currentAppVersionChunks = currentAppVersion.split('.'); +const { formatMessage } = i18nHelpers; const nextAppVersion = [ currentAppVersionChunks[0], parseInt(currentAppVersionChunks[1], 10) + 1, currentAppVersionChunks[2], ].join('.'); +const SELECTORS = { + DESCRIPTION: 'manualUpdate.description2', + OVERLAY: '.ManualUpdate_content', + VERSION_INFO: '.ManualUpdate_content .ManualUpdate_description p:nth-child(2)', +}; Given(/^There is a newer application version available$/, async function() { await this.client.execute(version => { @@ -28,7 +32,7 @@ When(/^Daedalus is stuck in connecting state$/, async function() { }); Then(/^I should see the "Manual Update" overlay$/, function() { - return this.client.waitForVisible('.ManualUpdate_content'); + return this.client.waitForVisible(SELECTORS.OVERLAY); }); Then( @@ -36,11 +40,11 @@ Then( async function() { const [renderedText] = await getVisibleTextsForSelector( this.client, - SELECTORS.MANUAL_UPDATE_VERSION_INFO + SELECTORS.VERSION_INFO ); - let expectedText = await i18n.formatMessage(this.client, { - id: 'manualUpdate.description2', + let expectedText = await formatMessage(this.client, { + id: SELECTORS.DESCRIPTION, values: { currentAppVersion, availableAppVersion: nextAppVersion, @@ -52,3 +56,14 @@ Then( expect(renderedText).to.equal(expectedText); } ); + +When(/^I trigger the apply-update endpoint$/, async function() { + await this.client.executeAsync(done => { + daedalus.api.ada + .applyUpdate() + .then(done) + .catch(e => { + throw e; + }); + }); +}); diff --git a/features/tests/e2e/steps/block-consolidation-page-steps.js b/tests/nodes/e2e/steps/block-consolidation-page.js similarity index 91% rename from features/tests/e2e/steps/block-consolidation-page-steps.js rename to tests/nodes/e2e/steps/block-consolidation-page.js index 7807b861aa..a2a9fa2823 100644 --- a/features/tests/e2e/steps/block-consolidation-page-steps.js +++ b/tests/nodes/e2e/steps/block-consolidation-page.js @@ -1,21 +1,23 @@ +// @flow import { When, Then } from 'cucumber'; import { expect } from 'chai'; -import { getVisibleTextsForSelector } from '../helpers/shared-helpers'; -import i18n from '../helpers/i18n-helpers'; +import { getVisibleTextsForSelector } from '../../../common/e2e/steps/helpers'; +import { i18nHelpers } from '../../../settings/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; const SELECTORS = { BLOCK_CONSOLIDATION_COMPONENT: '.BlockConsolidationStatus_component', - BLOCK_CONSOLIDATION_EXPLANATION: - '.BlockConsolidationStatus_content p:nth-child(3)', + BLOCK_CONSOLIDATION_EXPLANATION: '.BlockConsolidationStatus_content p:nth-child(3)', + EPOCHS_CONSOLIDATED: '.BlockConsolidationStatus_indicatorEpochsConsolidated p', EPOCHS_CONSOLIDATION_STATUS: '.BlockConsolidationStatus_epochs p span b', - EPOCHS_CONSOLIDATED: - '.BlockConsolidationStatus_indicatorEpochsConsolidated p', - TRAILING_BY_2_EPOCH: '.BlockConsolidationStatus_indicatorEpochsBehind p', MAXIMUM_EPOCH: '.BlockConsolidationStatus_fullEpoch', SYNC_PROGRESS: '.BlockConsolidationStatus_indicatorEpochsSynced p span', - SYNC_PROGRESS_LOADING_STATE: - '.BlockConsolidationStatus_indicatorContainerNoCurrentEpochs', + SYNC_PROGRESS_LOADING_STATE: '.BlockConsolidationStatus_indicatorContainerNoCurrentEpochs', + TRAILING_BY_2_EPOCH: '.BlockConsolidationStatus_indicatorEpochsBehind p', }; +const { formatMessage } = i18nHelpers; When(/^I open the Block Consolidation Status Dialog$/, async function() { await this.client.execute(() => @@ -66,7 +68,7 @@ Then( currentEpochBehind = `(${Math.max(currentEpochValue - 1, 0)})`; } - let expectedText = await i18n.formatMessage(this.client, { + let expectedText = await formatMessage(this.client, { id: consolidationText.message, values: { currentEpoch, @@ -195,7 +197,7 @@ Then( .catch(error => done(error)); }); const [expectedTextData] = data.hashes(); - const expectedText = await i18n.formatMessage(this.client, { + const expectedText = await formatMessage(this.client, { id: expectedTextData.message, values: { epochsSynced }, }); diff --git a/features/tests/e2e/steps/cardano-steps.js b/tests/nodes/e2e/steps/cardano-node.js similarity index 82% rename from features/tests/e2e/steps/cardano-steps.js rename to tests/nodes/e2e/steps/cardano-node.js index 496ba03065..376f811eae 100644 --- a/features/tests/e2e/steps/cardano-steps.js +++ b/tests/nodes/e2e/steps/cardano-node.js @@ -1,10 +1,7 @@ // @flow import { Given, Then } from 'cucumber'; import { CardanoNodeStates } from '../../../../source/common/types/cardano-node.types'; -import { - getCardanoNodeState, - waitForCardanoNodeToExit, -} from '../helpers/cardano-node-helpers'; +import { getCardanoNodeState, waitForCardanoNodeToExit } from './helpers'; Given(/^cardano-node is running$/, async function() { await this.client.waitUntil( diff --git a/features/tests/e2e/steps/app-steps.js b/tests/nodes/e2e/steps/daedalus-process.js similarity index 83% rename from features/tests/e2e/steps/app-steps.js rename to tests/nodes/e2e/steps/daedalus-process.js index 8015c1246d..808925846e 100644 --- a/features/tests/e2e/steps/app-steps.js +++ b/tests/nodes/e2e/steps/daedalus-process.js @@ -1,12 +1,12 @@ // @flow import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; -import type { Daedalus } from '../setup/global-types'; -import { waitUntilTextInSelector } from '../helpers/shared-helpers'; -import { waitForCardanoNodeToExit } from '../helpers/cardano-node-helpers'; -import { refreshClient, waitForDaedalusToExit } from '../helpers/app-helpers'; +import { waitUntilTextInSelector } from '../../../common/e2e/steps/helpers'; +import { refreshClient, waitForCardanoNodeToExit, waitForDaedalusToExit } from './helpers'; +import type { Daedalus } from '../../../types'; declare var daedalus: Daedalus; +const CONNECTING_TITLE = '.SyncingConnectingTitle_connecting h1'; Given(/^Daedalus is running$/, function() { expect(this.app.isRunning()).to.equal(true); @@ -53,7 +53,7 @@ Then(/^I should see the loading screen with "([^"]*)"$/, async function( message ) { await waitUntilTextInSelector(this.client, { - selector: '.SyncingConnectingTitle_connecting h1', + selector: CONNECTING_TITLE, text: message, }); }); diff --git a/features/tests/e2e/steps/data-layer-migration.js b/tests/nodes/e2e/steps/data-layer-migration.js similarity index 60% rename from features/tests/e2e/steps/data-layer-migration.js rename to tests/nodes/e2e/steps/data-layer-migration.js index 82b1f487f8..65a4376eb7 100644 --- a/features/tests/e2e/steps/data-layer-migration.js +++ b/tests/nodes/e2e/steps/data-layer-migration.js @@ -1,7 +1,13 @@ +// @flow import { Given, When, Then } from 'cucumber'; +import type { Daedalus } from '../../../types'; -const DATA_LAYER_MIGRATION_ACCEPTANCE_COMPONENT = - '.DataLayerMigrationForm_component'; +declare var daedalus: Daedalus; + +const SELECTORS = { + COMPONENT: '.DataLayerMigrationForm_component', + SUBMIT_BTN: '.DataLayerMigrationForm_submitButton', +}; Given(/^I haven't accepted the data layer migration$/, async function() { await this.client.execute(() => { @@ -10,12 +16,12 @@ Given(/^I haven't accepted the data layer migration$/, async function() { }); Then(/^I should see the Data Layer Migration screen$/, function() { - return this.client.waitForVisible(DATA_LAYER_MIGRATION_ACCEPTANCE_COMPONENT); + return this.client.waitForVisible(SELECTORS.COMPONENT); }); Then(/^I should not see the Data Layer Migration screen$/, function() { return this.client.waitForVisible( - DATA_LAYER_MIGRATION_ACCEPTANCE_COMPONENT, + SELECTORS.COMPONENT, null, true ); @@ -23,6 +29,6 @@ Then(/^I should not see the Data Layer Migration screen$/, function() { When(/^I click the migration button$/, function() { return this.waitAndClick( - `${DATA_LAYER_MIGRATION_ACCEPTANCE_COMPONENT} .DataLayerMigrationForm_submitButton` + `${SELECTORS.COMPONENT} ${SELECTORS.SUBMIT_BTN}` ); }); diff --git a/tests/nodes/e2e/steps/helpers.js b/tests/nodes/e2e/steps/helpers.js new file mode 100644 index 0000000000..cb204b9b12 --- /dev/null +++ b/tests/nodes/e2e/steps/helpers.js @@ -0,0 +1,42 @@ +// @flow +import { getProcessesByName } from '../../../../source/main/utils/processes'; +import type { Daedalus, WebdriverClient } from '../../../types'; + +declare var daedalus: Daedalus; + +const ACTIVE_RESTORE_NOTIFICATION = '.ActiveRestoreNotification'; + +export const getCardanoNodeState = async (client: WebdriverClient) => + (await client.execute(() => daedalus.stores.networkStatus.cardanoNodeState)).value; + +export const refreshClient = async (client: WebdriverClient) => { + await client.url(`file://${__dirname}/../../../../dist/renderer/index.html`); +}; + +export const waitForActiveRestoreNotification = ( + client: WebdriverClient, + { isHidden } : { isHidden: boolean } = {} +) => + client.waitForVisible( + ACTIVE_RESTORE_NOTIFICATION, + null, + isHidden + ); + +export const waitForCardanoNodeToExit = async (client: WebdriverClient) => + client.waitUntil( + async () => (await getProcessesByName('cardano-node')).length === 0, + 61000 + ); + +export const waitForDaedalusToExit = async ( + client: WebdriverClient, + timeout: number = 61000 +) => { + const daedalusProcessName = + process.platform === 'linux' ? 'electron' : 'Electron'; + return client.waitUntil( + async () => (await getProcessesByName(daedalusProcessName)).length === 0, + timeout + ); +}; diff --git a/tests/nodes/e2e/steps/inject-fault.js b/tests/nodes/e2e/steps/inject-fault.js new file mode 100644 index 0000000000..9029269e56 --- /dev/null +++ b/tests/nodes/e2e/steps/inject-fault.js @@ -0,0 +1,16 @@ +// @flow +import { When } from 'cucumber'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; + +When(/^I inject fault named "([^"]*)"$/, async function(faultName) { + await this.client.executeAsync((name, done) => { + daedalus.api.ada + .setCardanoNodeFault([name, true]) + .then(done) + .catch(e => { + throw e; + }); + }, faultName); +}); \ No newline at end of file diff --git a/features/tests/e2e/steps/local-time-difference-steps.js b/tests/nodes/e2e/steps/local-time-difference.js similarity index 71% rename from features/tests/e2e/steps/local-time-difference-steps.js rename to tests/nodes/e2e/steps/local-time-difference.js index 344be30a33..325c95e6e1 100644 --- a/features/tests/e2e/steps/local-time-difference-steps.js +++ b/tests/nodes/e2e/steps/local-time-difference.js @@ -1,7 +1,13 @@ +// @flow import { Given, Then } from 'cucumber'; -import { expectTextInSelector } from '../helpers/shared-helpers'; +import { expectTextInSelector } from '../../../common/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; -const selector = '.time-off'; +declare var daedalus: Daedalus; +const SELECTORS = { + ERROR_COMPONENT: '.SystemTimeError_component', + TIME_OFF: '.time-off', +}; Given('I set the local time difference to be {int} seconds', async function( seconds @@ -21,12 +27,13 @@ Then(/^the system time error overlay should be (hidden|visible)$/, function( ) { const isVisible = state === 'visible'; return this.client.waitForVisible( - '.SystemTimeError_component', + SELECTORS.ERROR_COMPONENT, null, !isVisible ); }); Then('the system time difference should be {string}', async function(text) { + let selector = SELECTORS.TIME_OFF; await expectTextInSelector(this.client, { selector, text }); }); diff --git a/features/tests/e2e/steps/no-disk-space.js b/tests/nodes/e2e/steps/no-disk-space.js similarity index 90% rename from features/tests/e2e/steps/no-disk-space.js rename to tests/nodes/e2e/steps/no-disk-space.js index 9d6d4ab5f2..59549e0c92 100644 --- a/features/tests/e2e/steps/no-disk-space.js +++ b/tests/nodes/e2e/steps/no-disk-space.js @@ -1,9 +1,12 @@ +// @flow import { Given, When, Then } from 'cucumber'; +import type { Daedalus } from '../../../types'; +declare var daedalus: Daedalus; let diskSpaceRequired; const HUNDRED_TB = 100 * 1e12; // 100 TB | unit: bytes -const ONE_KB = 1 * 1000; // 1 KB | unit: bytes const NO_DISK_SPACE_COMPONENT = '.NoDiskSpaceError_component'; +const ONE_KB = 1 * 1000; // 1 KB | unit: bytes Given(/^I set the required space to 100 TB$/, () => { diskSpaceRequired = HUNDRED_TB; diff --git a/features/tests/e2e/steps/node-update-notification-steps.js b/tests/nodes/e2e/steps/node-update-notification.js similarity index 58% rename from features/tests/e2e/steps/node-update-notification-steps.js rename to tests/nodes/e2e/steps/node-update-notification.js index 45fb725191..718b9bd1c8 100644 --- a/features/tests/e2e/steps/node-update-notification-steps.js +++ b/tests/nodes/e2e/steps/node-update-notification.js @@ -1,7 +1,10 @@ import { When, Then } from 'cucumber'; import { expect } from 'chai'; import { environment } from '../../../../source/main/environment'; -import { getVisibleTextsForSelector } from '../helpers/shared-helpers'; +import { getVisibleTextsForSelector } from '../../../common/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; const currentAppVersion = environment.version; @@ -10,10 +13,25 @@ const SELECTORS = { '.AutomaticUpdateNotification_description p span b:nth-child(1)', newAppVersionInfo: '.AutomaticUpdateNotification_description p span b:nth-child(2)', + acceptButton: '.AutomaticUpdateNotification_acceptButton', + postponeButton: '.AutomaticUpdateNotification_postponeButton', + nodeUpdateOverlay: '.AutomaticUpdateNotification_dialog', + nodeUpdateComponent: '.AutomaticUpdateNotification_overlay', }; Then('I should see the node update notification overlay', async function() { - return this.client.waitForVisible('.AutomaticUpdateNotification_dialog'); + return this.client.waitForVisible(SELECTORS.nodeUpdateOverlay); +}); + +When(/^I set next update version to "([^"]*)"$/, async function(applicationVersion) { + await this.client.executeAsync((applicationVersion, done) => { + daedalus.api.ada + .setNextUpdate(parseInt(applicationVersion)) + .then(done) + .catch(e => { + throw e; + }); + }, applicationVersion); }); When( @@ -29,8 +47,8 @@ When( ); expect(newAppVersionInfo.replace('v ', '')).to.equal(nextVersion); expect(currentAppVersionInfo.replace('v ', '')).to.equal(currentAppVersion); - this.client.waitForVisible('.AutomaticUpdateNotification_acceptButton'); - this.client.waitForVisible('.AutomaticUpdateNotification_postponeButton'); + this.client.waitForVisible(SELECTORS.acceptButton); + this.client.waitForVisible(SELECTORS.postponeButton); } ); @@ -43,16 +61,16 @@ When(/^I set next application version to "([^"]*)"$/, async function( }); When(/^I click the postpone update button$/, function() { - return this.waitAndClick('.AutomaticUpdateNotification_postponeButton'); + return this.waitAndClick(SELECTORS.postponeButton); }); When(/^I click the accept update button$/, function() { - return this.waitAndClick('.AutomaticUpdateNotification_acceptButton'); + return this.waitAndClick(SELECTORS.acceptButton); }); Then(/^I should not see the notification component anymore$/, function() { return this.client.waitForVisible( - '.AutomaticUpdateNotification_overlay', + SELECTORS.nodeUpdateComponent, null, true ); diff --git a/features/tests/e2e/steps/trouble-connecting-notification-steps.js b/tests/nodes/e2e/steps/trouble-connecting-notification.js similarity index 81% rename from features/tests/e2e/steps/trouble-connecting-notification-steps.js rename to tests/nodes/e2e/steps/trouble-connecting-notification.js index 0b1867d48c..3faef6089a 100644 --- a/features/tests/e2e/steps/trouble-connecting-notification-steps.js +++ b/tests/nodes/e2e/steps/trouble-connecting-notification.js @@ -1,10 +1,14 @@ +// @flow import { Then, When } from 'cucumber'; -import { waitUntilTextInSelector } from '../helpers/shared-helpers'; +import { waitUntilTextInSelector } from '../../../common/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; const SELECTORS = { + REPORT_ISSUE_BTN: '.ReportIssue_actionButton.reportIssueButton', + REPORT_ISSUE_HEADER: '.ReportIssue_reportIssueText', SYNCING_CONNECTING_COMPONENT: '.SyncingConnecting_component', - REPORT_ISSUE_TEXT_H1: '.ReportIssue_reportIssueText', - REPORT_ISSUE_BUTTON: '.ReportIssue_actionButton.reportIssueButton', }; Then(/^I should not see the loading screen$/, async function() { @@ -19,14 +23,14 @@ Then( /^I should see the report issue notification displaying "([^"]*)"$/, async function(text) { await waitUntilTextInSelector(this.client, { - selector: SELECTORS.REPORT_ISSUE_TEXT_H1, + selector: SELECTORS.REPORT_ISSUE_HEADER, text, }); } ); Then(/^I should not see the report issue notification$/, async function() { - await this.client.waitForVisible(SELECTORS.REPORT_ISSUE_TEXT_H1, null, true); + await this.client.waitForVisible(SELECTORS.REPORT_ISSUE_HEADER, null, true); }); Then(/^The report issue button should be (hidden|visible)$/, async function( @@ -34,7 +38,7 @@ Then(/^The report issue button should be (hidden|visible)$/, async function( ) { const waitForHidden = state === 'hidden'; await this.client.waitForVisible( - SELECTORS.REPORT_ISSUE_BUTTON, + SELECTORS.REPORT_ISSUE_BTN, null, waitForHidden ); diff --git a/features/tests/e2e/steps/trouble-syncing-notification-steps.js b/tests/nodes/e2e/steps/trouble-syncing-notification.js similarity index 66% rename from features/tests/e2e/steps/trouble-syncing-notification-steps.js rename to tests/nodes/e2e/steps/trouble-syncing-notification.js index f68180537b..a6e95aad7a 100644 --- a/features/tests/e2e/steps/trouble-syncing-notification-steps.js +++ b/tests/nodes/e2e/steps/trouble-syncing-notification.js @@ -1,5 +1,11 @@ +// @flow import { When, Then } from 'cucumber'; -import { waitUntilTextInSelector } from '../helpers/shared-helpers'; +import { waitUntilTextInSelector } from '../../../common/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; + +const SYNCING_STATUS_HEADER = '.SyncingConnectingTitle_syncing h1'; When( /^I arbitrarily set the local block height to half the network block height$/, @@ -16,7 +22,7 @@ When( Then(/^I should see the syncing status with "([^"]*)"$/, async function(text) { await waitUntilTextInSelector(this.client, { - selector: '.SyncingConnectingTitle_syncing h1', + selector: SYNCING_STATUS_HEADER, text, }); }); diff --git a/features/tests/e2e/steps/wallet-recovery-phrase-verification-steps.js b/tests/nodes/e2e/steps/wallet-recovery-phrase-verification-steps.js similarity index 100% rename from features/tests/e2e/steps/wallet-recovery-phrase-verification-steps.js rename to tests/nodes/e2e/steps/wallet-recovery-phrase-verification-steps.js diff --git a/features/tests/e2e/documents/paper_wallet_certificates/.gitkeep b/tests/paper-wallets/e2e/documents/.gitkeep similarity index 100% rename from features/tests/e2e/documents/paper_wallet_certificates/.gitkeep rename to tests/paper-wallets/e2e/documents/.gitkeep diff --git a/features/paper-wallets-certificate.feature b/tests/paper-wallets/e2e/features/paper-wallets-certificate.feature similarity index 100% rename from features/paper-wallets-certificate.feature rename to tests/paper-wallets/e2e/features/paper-wallets-certificate.feature diff --git a/features/tests/e2e/steps/paper-wallets-certificate-steps.js b/tests/paper-wallets/e2e/steps/paper-wallets-certificate.js similarity index 95% rename from features/tests/e2e/steps/paper-wallets-certificate-steps.js rename to tests/paper-wallets/e2e/steps/paper-wallets-certificate.js index 5868f8eef5..3e9cec58a7 100644 --- a/features/tests/e2e/steps/paper-wallets-certificate-steps.js +++ b/tests/paper-wallets/e2e/steps/paper-wallets-certificate.js @@ -1,12 +1,16 @@ +// @flow import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; import path from 'path'; -import { fillOutWalletSendForm } from '../helpers/wallets-helpers'; -import { waitUntilTextInSelector } from '../helpers/shared-helpers'; +import { fillOutWalletSendForm } from '../../../wallets/e2e/steps/helpers'; +import { waitUntilTextInSelector } from '../../../common/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; const paperWalletCertificatePath = path.resolve( __dirname, - '../documents/paper_wallet_certificates/paper-wallet-certificate.pdf' + '../documents/paper-wallet-certificate.pdf' ); Given(/^I see the "Certificate Generation Instructions" dialog$/, function() { diff --git a/features/accept-terms-of-use.feature b/tests/settings/e2e/features/accept-terms-of-use.feature similarity index 100% rename from features/accept-terms-of-use.feature rename to tests/settings/e2e/features/accept-terms-of-use.feature diff --git a/features/select-language.feature b/tests/settings/e2e/features/select-language.feature similarity index 100% rename from features/select-language.feature rename to tests/settings/e2e/features/select-language.feature diff --git a/features/wallet-settings.feature b/tests/settings/e2e/features/wallet-settings.feature similarity index 100% rename from features/wallet-settings.feature rename to tests/settings/e2e/features/wallet-settings.feature diff --git a/tests/settings/e2e/steps/basic-setup.js b/tests/settings/e2e/steps/basic-setup.js new file mode 100644 index 0000000000..29790a233a --- /dev/null +++ b/tests/settings/e2e/steps/basic-setup.js @@ -0,0 +1,15 @@ +// @flow +import { Given } from 'cucumber'; +import { languageSelectionHelpers, migrationHelpers, termsOfUseHelpers } from './helpers'; + +const { acceptMigration } = migrationHelpers; +const { acceptTerms } = termsOfUseHelpers; +const { ensureLanguageIsSelected } = languageSelectionHelpers; + +Given(/^I have completed the basic setup$/, async function() { + await ensureLanguageIsSelected(this.client, { + language: 'en-US', + }); + await acceptTerms(this.client); + await acceptMigration(this.client); +}); diff --git a/features/tests/e2e/steps/general-settings-steps.js b/tests/settings/e2e/steps/general-settings.js similarity index 70% rename from features/tests/e2e/steps/general-settings-steps.js rename to tests/settings/e2e/steps/general-settings.js index cbaf03b380..8efc3ea9be 100644 --- a/features/tests/e2e/steps/general-settings-steps.js +++ b/tests/settings/e2e/steps/general-settings.js @@ -1,15 +1,9 @@ -import { Given, When, Then } from 'cucumber'; -import _ from 'lodash'; -import { navigateTo, waitUntilUrlEquals } from '../helpers/route-helpers'; - -Given(/^I am on the General Settings "([^"]*)" screen$/, async function( - screen -) { - await navigateTo.call(this, `/settings/${screen}`); -}); +// @flow +import { When, Then } from 'cucumber'; +import { camelCase } from 'lodash'; When(/^I click on secondary menu (.*) item$/, async function(buttonName) { - const buttonSelector = `.SettingsMenuItem_component.${_.camelCase( + const buttonSelector = `.SettingsMenuItem_component.${camelCase( buttonName )}`; await this.client.waitForVisible(buttonSelector); @@ -24,12 +18,6 @@ When(/^I open General Settings language selection dropdown$/, async function() { await this.client.click('.GeneralSettings_component .SimpleInput_input'); }); -Then(/^I should see General Settings "([^"]*)" screen$/, async function( - screenName -) { - return waitUntilUrlEquals.call(this, `/settings/${screenName}`); -}); - Then(/^I should see Japanese language as selected$/, async function() { return this.client.waitUntil(async () => { const selectedLanguage = await this.client.getValue( diff --git a/tests/settings/e2e/steps/helpers.js b/tests/settings/e2e/steps/helpers.js new file mode 100644 index 0000000000..b9e92bee06 --- /dev/null +++ b/tests/settings/e2e/steps/helpers.js @@ -0,0 +1,88 @@ +// @flow +import type { Daedalus, WebdriverClient } from '../../../types'; + +const DATA_LAYER_MIGRATION_ACCEPTANCE_COMPONENT = '.DataLayerMigrationForm_component'; +const DEFAULT_LANGUAGE = 'en-US'; +const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; +const TERMS_OF_USE_FORM = '.TermsOfUseForm_component'; + +declare var daedalus: Daedalus; + +export const i18nHelpers = { + formatMessage: async ( + client: WebdriverClient, + { id, values }: { id: string, values?: Object } + ) => { + const translation = await client.execute( + (translationId, translationValues) => { + const IntlProvider = require('react-intl').IntlProvider; // eslint-disable-line + const locale = daedalus.stores.profile.currentLocale; + const messages = daedalus.translations; + const intlProvider = new IntlProvider( + { locale, messages: messages[locale] }, + {} + ); + return intlProvider + .getChildContext() + .intl.formatMessage({ id: translationId }, translationValues); + }, + id, + values || {} + ); + return translation.value; + }, + setActiveLanguage: async ( + client: WebdriverClient, + { language }: { language: string } = {} + ) => + client.execute(locale => { + daedalus.actions.profile.updateLocale.trigger({ locale }); + }, language || DEFAULT_LANGUAGE), +}; + +export const languageSelectionHelpers = { + waitForVisible: async ( + client: WebdriverClient, + { isHidden }: { isHidden: boolean } = {} + ) => + client.waitForVisible(LANGUAGE_SELECTION_FORM, null, isHidden), + ensureLanguageIsSelected: async ( + client: WebdriverClient, + { language }: { language: string } = {} + ) => { + await i18nHelpers.setActiveLanguage(client, { language }); + await languageSelectionHelpers.waitForVisible(client, { isHidden: true }); + }, +}; + +export const migrationHelpers = { + waitForVisible: async ( + client: WebdriverClient, + { isHidden } : { isHidden: boolean } = {} + ) => + client.waitForVisible( + DATA_LAYER_MIGRATION_ACCEPTANCE_COMPONENT, + null, + isHidden + ), + acceptMigration: async (client: WebdriverClient) => { + await client.execute(() => { + daedalus.actions.profile.acceptDataLayerMigration.trigger(); + }); + await migrationHelpers.waitForVisible(client, { isHidden: true }); + }, +}; + +export const termsOfUseHelpers = { + waitForVisible: async ( + client: WebdriverClient, + { isHidden } : { isHidden: boolean } = {} + ) => + client.waitForVisible(TERMS_OF_USE_FORM, null, isHidden), + acceptTerms: async (client: WebdriverClient) => { + await client.execute(() => { + daedalus.actions.profile.acceptTermsOfUse.trigger(); + }); + await termsOfUseHelpers.waitForVisible(client, { isHidden: true }); + }, +}; diff --git a/features/tests/e2e/steps/select-language-steps.js b/tests/settings/e2e/steps/select-language.js similarity index 84% rename from features/tests/e2e/steps/select-language-steps.js rename to tests/settings/e2e/steps/select-language.js index ad55213a70..b3029a2df8 100644 --- a/features/tests/e2e/steps/select-language-steps.js +++ b/tests/settings/e2e/steps/select-language.js @@ -1,11 +1,15 @@ +// @flow import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; -import languageSelection from '../helpers/language-selection-helpers'; +import { languageSelectionHelpers } from './helpers'; +import type { Daedalus } from '../../../types'; +declare var daedalus: Daedalus; +const { ensureLanguageIsSelected } = languageSelectionHelpers; const LANGUAGE_SELECTION_FORM = '.LanguageSelectionForm_component'; Given(/^I have selected English language$/, async function() { - await languageSelection.ensureLanguageIsSelected(this.client, { + await ensureLanguageIsSelected(this.client, { language: 'en-US', }); }); diff --git a/features/tests/e2e/steps/settings-steps.js b/tests/settings/e2e/steps/settings.js similarity index 95% rename from features/tests/e2e/steps/settings-steps.js rename to tests/settings/e2e/steps/settings.js index 324b65d81d..82cc8f0b9a 100644 --- a/features/tests/e2e/steps/settings-steps.js +++ b/tests/settings/e2e/steps/settings.js @@ -1,10 +1,14 @@ +// @flow import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; -import { navigateTo } from '../helpers/route-helpers'; +import { navigateTo } from '../../../navigation/e2e/steps/helpers'; import { waitUntilWaletNamesEqual, getNameOfActiveWalletInSidebar, -} from '../helpers/wallets-helpers'; +} from '../../../wallets/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; Given(/^I am on the settings screen$/, async function() { await navigateTo.call(this, '/settings'); diff --git a/features/tests/e2e/steps/accept-terms-of-use-steps.js b/tests/settings/e2e/steps/terms-of-use.js similarity index 84% rename from features/tests/e2e/steps/accept-terms-of-use-steps.js rename to tests/settings/e2e/steps/terms-of-use.js index 08e612a560..acb9fffcec 100644 --- a/features/tests/e2e/steps/accept-terms-of-use-steps.js +++ b/tests/settings/e2e/steps/terms-of-use.js @@ -1,11 +1,16 @@ +// @flow import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; -import termsOfUse from '../helpers/terms-of-use-helpers'; +import { termsOfUseHelpers } from './helpers'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; const TERMS_OF_USE_FORM = '.TermsOfUseForm_component'; +const { acceptTerms } = termsOfUseHelpers; Given(/^I have accepted "Terms of use"$/, async function() { - await termsOfUse.acceptTerms(this.client); + await acceptTerms(this.client); }); Given(/^I didnt accept "Terms of use"$/, async function() { diff --git a/features/generate-filename-with-timestamp.feature b/tests/settings/unit/features/generate-filename-with-timestamp.feature similarity index 100% rename from features/generate-filename-with-timestamp.feature rename to tests/settings/unit/features/generate-filename-with-timestamp.feature diff --git a/features/tests/unit/steps/generate-filename-with-timestamp-steps.js b/tests/settings/unit/steps/file-names.js similarity index 85% rename from features/tests/unit/steps/generate-filename-with-timestamp-steps.js rename to tests/settings/unit/steps/file-names.js index b1ba200d24..74307084cb 100644 --- a/features/tests/unit/steps/generate-filename-with-timestamp-steps.js +++ b/tests/settings/unit/steps/file-names.js @@ -8,9 +8,14 @@ import { const getDataFromFunction = props => { const filename = generateFileNameWithTimestamp(props); - const prefix = filename.match(/^[^-]*[^ -]/i)[0]; - const extension = filename.match(/\.[0-9a-z]+$/i)[0].replace('.', ''); - const isUTC = !!filename.match(`Z.${extension}`); + let prefix = filename.match(/^[^-]*[^ -]/i); + let extension = filename.match(/\.[0-9a-z]+$/i); + let isUTC = false; + + if (prefix) { prefix = prefix[0]; } + if (extension) { extension = extension[0].replace('.', ''); } + if (extension) { isUTC = !!filename.match(`Z.${extension}`); } + return { filename, prefix, diff --git a/features/tests/e2e/setup/electron.js b/tests/setup.js similarity index 75% rename from features/tests/e2e/setup/electron.js rename to tests/setup.js index 70e53d00f4..cb4adb3f46 100644 --- a/features/tests/e2e/setup/electron.js +++ b/tests/setup.js @@ -1,3 +1,4 @@ +// @flow import path from 'path'; import { Application } from 'spectron'; import { @@ -9,16 +10,19 @@ import { } from 'cucumber'; import electronPath from 'electron'; import fakeDialog from 'spectron-fake-dialog'; -import { TEST } from '../../../../source/common/types/environment.types'; import { generateScreenshotFilePath, getTestNameFromTestFile, saveScreenshot, -} from '../helpers/screenshot'; -import { refreshClient } from '../helpers/app-helpers'; +} from './common/e2e/steps/helpers'; +import { setNewsFeedIsOpen, resetTestNews } from './news/e2e/steps/newsfeed-steps'; +import { refreshClient } from './nodes/e2e/steps/helpers'; +import { TEST } from '../source/common/types/environment.types'; +import type { Daedalus } from './types'; /* eslint-disable consistent-return */ +declare var daedalus: Daedalus; const context = {}; const DEFAULT_TIMEOUT = 20000; let scenariosCount = 0; @@ -36,7 +40,7 @@ const printMainProcessLogs = () => const defaultWalletKeyFilePath = path.resolve( __dirname, - '../documents/default-wallet.key' + './wallets/e2e/documents/default-wallet.key' ); const startApp = async () => { @@ -50,9 +54,9 @@ const startApp = async () => { waitTimeout: DEFAULT_TIMEOUT, chromeDriverLogPath: path.join( __dirname, - '../../../../logs/chrome-driver.log' + '../logs/chrome-driver.log' ), - webdriverLogPath: path.join(__dirname, '../../../../logs/webdriver'), + webdriverLogPath: path.join(__dirname, '../logs/webdriver'), }); fakeDialog.apply(app); await app.start(); @@ -130,6 +134,48 @@ Before({ timeout: DEFAULT_TIMEOUT * 2 }, async function(testCase) { }); }); +// adds context object to webdriver +Before(function() { + this.context = {}; +}); + +Before({ tags: '@newsfeed' }, function() { + setNewsFeedIsOpen(this.client, false); + resetTestNews(this.client); +}); + +// adds waitAndClick method to webdriver +Before(function() { + this.waitAndClick = async (selector, ...waitArgs) => { + await this.client.waitForVisible(selector, ...waitArgs); + return this.client.click(selector); + }; +}); + +// ads intl method to webdriver +Before(function() { + this.intl = async (translationId, translationValues = {}) => { + const translation = await this.client.execute( + (id, values) => { + const IntlProvider = require('react-intl').IntlProvider; // eslint-disable-line + const locale = daedalus.stores.profile.currentLocale; + const messages = daedalus.translations; + const intlProvider = new IntlProvider( + { locale, messages: messages[locale] }, + {} + ); + return intlProvider + .getChildContext() + .intl.formatMessage({ id }, values); + }, + translationId, + translationValues + ); + return translation.value; + }; +}); + + // this ensures that the spectron instance of the app restarts // after the node update acceptance test shuts it down via 'kill-process' // eslint-disable-next-line prefer-arrow-callback diff --git a/features/send-money-to-receiver.feature b/tests/transactions/e2e/features/send-money-to-receiver.feature similarity index 100% rename from features/send-money-to-receiver.feature rename to tests/transactions/e2e/features/send-money-to-receiver.feature diff --git a/features/transactions-display.feature b/tests/transactions/e2e/features/transactions-display.feature similarity index 100% rename from features/transactions-display.feature rename to tests/transactions/e2e/features/transactions-display.feature diff --git a/features/wallet-utxos.feature b/tests/transactions/e2e/features/wallet-utxos.feature similarity index 100% rename from features/wallet-utxos.feature rename to tests/transactions/e2e/features/wallet-utxos.feature diff --git a/features/tests/e2e/steps/transactions-steps.js b/tests/transactions/e2e/steps/transactions.js similarity index 93% rename from features/tests/e2e/steps/transactions-steps.js rename to tests/transactions/e2e/steps/transactions.js index cea7a72da8..c54052b8b8 100644 --- a/features/tests/e2e/steps/transactions-steps.js +++ b/tests/transactions/e2e/steps/transactions.js @@ -1,3 +1,4 @@ +// @flow import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; import BigNumber from 'bignumber.js/bignumber'; @@ -5,8 +6,11 @@ import { DECIMAL_PLACES_IN_ADA, LOVELACES_PER_ADA, } from '../../../../source/renderer/app/config/numbersConfig'; -import { getVisibleTextsForSelector } from '../helpers/shared-helpers'; -import { getWalletByName } from '../helpers/wallets-helpers'; +import { getVisibleTextsForSelector } from '../../../common/e2e/steps/helpers'; +import { getWalletByName } from '../../../wallets/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; // This step ensures sequential creation of given transactions // use only when the order is important because it's slower! diff --git a/features/tests/e2e/steps/wallets-utxos-steps.js b/tests/transactions/e2e/steps/utxos.js similarity index 81% rename from features/tests/e2e/steps/wallets-utxos-steps.js rename to tests/transactions/e2e/steps/utxos.js index e72488dd9a..43c07041de 100644 --- a/features/tests/e2e/steps/wallets-utxos-steps.js +++ b/tests/transactions/e2e/steps/utxos.js @@ -1,7 +1,11 @@ +// @flow import { Then } from 'cucumber'; import { expect } from 'chai'; -import { getVisibleTextsForSelector } from '../helpers/shared-helpers'; +import { getVisibleTextsForSelector } from '../../../common/e2e/steps/helpers'; import { getWalletUtxosTotalAmount } from '../../../../source/renderer/app/utils/utxoUtils'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; const container = '.WalletUtxo_container'; @@ -38,18 +42,18 @@ Then( this.client, selectors.walletUtxosAmount ); - const { value: { expextedWalletAmount, histogram }, - } = await this.client.executeAsync(done => + } = await this.client.executeAsync(done => { + const histogram = daedalus.stores.walletSettings.walletUtxos + ? daedalus.stores.walletSettings.walletUtxos.histogram + : null; done({ expextedWalletAmount: daedalus.stores.wallets.activeValue, - histogram: daedalus.stores.walletSettings.walletUtxos.histogram, - }) - ); - + histogram, + }); + }); const expectedWalletUtxosAmount = getWalletUtxosTotalAmount(histogram); - expect(expextedWalletAmount).to.equal(renderedWalletAmount); expect(expectedWalletUtxosAmount).to.equal( parseInt(renderedWalletUtxosAmount, 10) diff --git a/features/utxos-chart.feature b/tests/transactions/unit/features/utxos-chart.feature similarity index 100% rename from features/utxos-chart.feature rename to tests/transactions/unit/features/utxos-chart.feature diff --git a/features/tests/unit/setup/utxo-helpers.js b/tests/transactions/unit/steps/helpers.js similarity index 62% rename from features/tests/unit/setup/utxo-helpers.js rename to tests/transactions/unit/steps/helpers.js index 477ef2f6a0..707dbc37e4 100644 --- a/features/tests/unit/setup/utxo-helpers.js +++ b/tests/transactions/unit/steps/helpers.js @@ -1,5 +1,6 @@ -export const getHistogramFromTable = data => { - const histogram = {}; +// @flow +export const getHistogramFromTable = (data: Object) => { + let histogram = {}; data.hashes().forEach(({ walletAmount, walletUtxosAmount }) => { histogram[walletAmount] = walletUtxosAmount; }); diff --git a/features/tests/unit/steps/utxos-chart-steps.js b/tests/transactions/unit/steps/utxos-chart.js similarity index 98% rename from features/tests/unit/steps/utxos-chart-steps.js rename to tests/transactions/unit/steps/utxos-chart.js index a7a93147ef..69eb1d4d3e 100644 --- a/features/tests/unit/steps/utxos-chart-steps.js +++ b/tests/transactions/unit/steps/utxos-chart.js @@ -9,7 +9,7 @@ import { getUtxoWalletPrettyAmount, getWalletUtxosTotalAmount, } from '../../../../source/renderer/app/utils/utxoUtils'; -import { getHistogramFromTable } from '../setup/utxo-helpers'; +import { getHistogramFromTable } from './helpers'; /* eslint-disable no-unused-expressions */ Given('the `getUtxoChartData` function receives the following props:', function( diff --git a/tests/types.js b/tests/types.js new file mode 100644 index 0000000000..cc09c76a2d --- /dev/null +++ b/tests/types.js @@ -0,0 +1,41 @@ +// @flow +import { defineParameterType } from 'cucumber'; +import type { Api } from '../source/renderer/app/api'; +import type { ActionsMap } from '../source/renderer/app/actions'; +import type { StoresMap } from '../source/renderer/app/stores'; + +// Add {bool} parameter type +defineParameterType({ + name: 'bool', + regexp: /true|false/, + transformer: b => b === 'true', +}); + +export type Daedalus = { + actions: ActionsMap, + api: Api, + environment: Object, + reset: Function, + stores: StoresMap, + translations: Object, + utils: { + crypto: { + generateMnemonic: Function + } + }, +}; + +export type WebdriverExecuteResult = { value: T }; + +export type WebdriverClient = { + click: (selector: string) => Promise, + elements: (selector: string) => Promise, + execute: (script: Function, ...scriptArgs: Array) => WebdriverExecuteResult, + executeAsync: (script: Function, ...scriptArgs: Array) => Promise>, + getText: (selector: string) => Promise, + url: (url: string) => Promise, + waitForEnabled: (selector: string, ms?: number | null, reverse?: boolean) => Promise, + waitForText: (selector: string) => Promise, + waitForVisible: (target: string, ms?: number | null, reverse?: boolean) => Promise, + waitUntil: (script: Function, timeout?: number) => Promise, +}; diff --git a/tests/wallets/e2e/documents/default-wallet.json b/tests/wallets/e2e/documents/default-wallet.json new file mode 100644 index 0000000000..41932c06d1 --- /dev/null +++ b/tests/wallets/e2e/documents/default-wallet.json @@ -0,0 +1,14 @@ +{ + "wallet": { + "accounts": [{ "name": "Genesis account", "index": 2147483648 }], + "walletSecretKey": "WIAwbsQgbz9X0WhvOnVeH+yRs7Ri93ESTdMspBHzeLnPUR6hLZL/NazfB40z2x8FZhLwNIt83DCuMR1nGG+ZqvsD/ouyzg3ec729fnrqEMO4A+qPTJmpiRgQZfYO2KDJDRxLtMyofXl90VVZOEke/QddnZ8CGHoR/lCemJgZuvzBpw==", + "walletMeta": { + "name": "Imported Wallet", + "assurance": "normal", + "unit": "ADA" + }, + "passwordHash": "WGQxNHw4fDF8V0NERGRHY0JGcThzelVyeFdza00wM1VjYnloeVBBQXBvdWtwdWFsUTExNGVFdz09fFJXMk5kUmVJYmg2REtsa2lsWG8rQ1lvTStRZmJkMzRmRVd0MG4rSy82YUU9" + }, + "fileType": "WALLETS_EXPORT", + "fileVersion": "1.0.0" +} diff --git a/features/tests/e2e/documents/default-wallet.key b/tests/wallets/e2e/documents/default-wallet.key similarity index 100% rename from features/tests/e2e/documents/default-wallet.key rename to tests/wallets/e2e/documents/default-wallet.key diff --git a/features/tests/e2e/documents/default-wallet.key.lock b/tests/wallets/e2e/documents/default-wallet.key.lock similarity index 100% rename from features/tests/e2e/documents/default-wallet.key.lock rename to tests/wallets/e2e/documents/default-wallet.key.lock diff --git a/features/add-wallet-via-sidebar.feature b/tests/wallets/e2e/features/add-wallet-via-sidebar.feature similarity index 100% rename from features/add-wallet-via-sidebar.feature rename to tests/wallets/e2e/features/add-wallet-via-sidebar.feature diff --git a/features/delete-wallet-via-settings.feature b/tests/wallets/e2e/features/delete-wallet-via-settings.feature similarity index 100% rename from features/delete-wallet-via-settings.feature rename to tests/wallets/e2e/features/delete-wallet-via-settings.feature diff --git a/features/import-wallet-via-sidebar.feature b/tests/wallets/e2e/features/import-wallet-via-sidebar.feature similarity index 100% rename from features/import-wallet-via-sidebar.feature rename to tests/wallets/e2e/features/import-wallet-via-sidebar.feature diff --git a/features/restore-wallet-via-sidebar.feature b/tests/wallets/e2e/features/restore-wallet-via-sidebar.feature similarity index 100% rename from features/restore-wallet-via-sidebar.feature rename to tests/wallets/e2e/features/restore-wallet-via-sidebar.feature diff --git a/features/wallets-limit.feature b/tests/wallets/e2e/features/wallets-limit.feature similarity index 100% rename from features/wallets-limit.feature rename to tests/wallets/e2e/features/wallets-limit.feature diff --git a/features/wallets-ordering.feature b/tests/wallets/e2e/features/wallets-ordering.feature similarity index 100% rename from features/wallets-ordering.feature rename to tests/wallets/e2e/features/wallets-ordering.feature diff --git a/features/tests/e2e/helpers/wallets-helpers.js b/tests/wallets/e2e/steps/helpers.js similarity index 66% rename from features/tests/e2e/helpers/wallets-helpers.js rename to tests/wallets/e2e/steps/helpers.js index 4718d893bd..32cf6451f8 100644 --- a/features/tests/e2e/helpers/wallets-helpers.js +++ b/tests/wallets/e2e/steps/helpers.js @@ -1,70 +1,15 @@ -import { expect } from 'chai'; +// @flow +import { expectTextInSelector, waitAndClick } from '../../../common/e2e/steps/helpers'; +import { WalletSyncStateTags } from '../../../../source/renderer/app/domains/Wallet'; +import type { Daedalus, WebdriverClient } from '../../../types'; -export const getNameOfActiveWalletInSidebar = async function() { - await this.client.waitForVisible('.SidebarWalletMenuItem_active'); - return this.client.getText( - '.SidebarWalletMenuItem_active .SidebarWalletMenuItem_title' - ); -}; - -export const expectActiveWallet = async function(walletName) { - const displayedWalletName = await getNameOfActiveWalletInSidebar.call(this); - expect(displayedWalletName.toLowerCase().trim()).to.equal( - walletName.toLowerCase().trim() - ); -}; - -export const fillOutWalletSendForm = async function(values) { - const formSelector = '.WalletSendForm_component'; - await this.client.setValue( - `${formSelector} .receiver .SimpleInput_input`, - values.address - ); - await this.client.setValue( - `${formSelector} .amount .SimpleInput_input`, - values.amount - ); - if (values.spendingPassword) { - await this.client.setValue( - `${formSelector} .spendingPassword .SimpleInput_input`, - values.spendingPassword - ); - } - this.walletSendFormValues = values; -}; - -export const getWalletByName = function(walletName) { - return this.wallets.find(w => w.name === walletName); -}; +declare var daedalus: Daedalus; -export const waitUntilWaletNamesEqual = function(walletName) { - const context = this; - return context.client.waitUntil(async () => { - const currentWalletName = await getNameOfActiveWalletInSidebar.call( - context - ); - return currentWalletName === walletName; - }); -}; +const ADD_WALLET = '.WalletAdd'; +const IMPORT_WALLET_BUTTON = '.importWalletButton'; +const IMPORT_WALLET_DIALOG = '.WalletFileImportDialog'; -export const waitUntilWalletIsLoaded = async function(walletName) { - let wallet = null; - const context = this; - await context.client.waitUntil(async () => { - const result = await context.client.execute( - name => daedalus.stores.wallets.getWalletByName(name), - walletName - ); - if (result.value) { - wallet = result.value; - return true; - } - return false; - }); - return wallet; -}; - -export const addOrSetWalletsForScenario = function(wallet) { +export const addOrSetWalletsForScenario = function(wallet: Object) { this.wallet = wallet; if (this.wallets != null) { this.wallets.push(this.wallet); @@ -73,25 +18,15 @@ export const addOrSetWalletsForScenario = function(wallet) { } }; -export const importWalletWithFunds = async ( - client, - { keyFilePath, password } -) => - client.executeAsync( - (filePath, spendingPassword, done) => { - daedalus.api.ada - .importWalletFromKey({ filePath, spendingPassword }) - .then(() => - daedalus.stores.wallets - .refreshWalletsData() - .then(done) - .catch(error => done(error)) - ) - .catch(error => done(error)); - }, - keyFilePath, - password - ); +export const addWalletHelpers = { + waitForVisible: ( + client: WebdriverClient, + { isHidden } : { isHidden: boolean } = {} + ) => + client.waitForVisible(ADD_WALLET, null, isHidden), + clickImportButton: (client: WebdriverClient) => + waitAndClick(client, `${ADD_WALLET} ${IMPORT_WALLET_BUTTON}`), +}; const createWalletsAsync = async (table, context) => { const result = await context.client.executeAsync((wallets, done) => { @@ -133,6 +68,18 @@ const createWalletsAsync = async (table, context) => { } }; +export const createWallets = async ( + wallets: Array<{}>, + context: Object, + options: Object = {} +) => { + if (options.sequentially === true) { + await createWalletsSequentially(wallets, context); + } else { + await createWalletsAsync(wallets, context); + } +}; + const createWalletsSequentially = async (wallets, context) => { context.wallets = []; for (const walletData of wallets) { @@ -160,10 +107,115 @@ const createWalletsSequentially = async (wallets, context) => { } }; -export const createWallets = async (wallets, context, options = {}) => { - if (options.sequentially === true) { - await createWalletsSequentially(wallets, context); - } else { - await createWalletsAsync(wallets, context); +export const fillOutWalletSendForm = async function(values: Object) { + const formSelector = '.WalletSendForm_component'; + await this.client.setValue( + `${formSelector} .receiver .SimpleInput_input`, + values.address + ); + await this.client.setValue( + `${formSelector} .amount .SimpleInput_input`, + values.amount + ); + if (values.spendingPassword) { + await this.client.setValue( + `${formSelector} .spendingPassword .SimpleInput_input`, + values.spendingPassword + ); } + this.walletSendFormValues = values; +}; + +export const getNameOfActiveWalletInSidebar = async function() { + await this.client.waitForVisible('.SidebarWalletMenuItem_active'); + return this.client.getText( + '.SidebarWalletMenuItem_active .SidebarWalletMenuItem_title' + ); +}; + +export const getWalletByName = function(walletName: string) { + return this.wallets.find(w => w.name === walletName); +}; + +export const importWalletHelpers = { + waitForDialog: ( + client: WebdriverClient, + { isHidden } : { isHidden: boolean } = {} + ) => + client.waitForVisible(IMPORT_WALLET_DIALOG, null, isHidden), + clickImport: ( + client: WebdriverClient + ) => + waitAndClick(client, `${IMPORT_WALLET_DIALOG} .primary`), + expectError: ( + client: WebdriverClient, + { error }: { error: string } + ) => + expectTextInSelector(client, { + selector: `${IMPORT_WALLET_DIALOG}_error`, + text: error, + }), +}; + +export const importWalletWithFunds = async ( + client: WebdriverClient, + { keyFilePath, password }: { keyFilePath: string, password: ?string } +) => + client.executeAsync( + (filePath, spendingPassword, done) => { + daedalus.api.ada + .importWalletFromKey({ filePath, spendingPassword }) + .then(() => + daedalus.stores.wallets + .refreshWalletsData() + .then(done) + .catch(error => done(error)) + ) + .catch(error => done(error)); + }, + keyFilePath, + password + ); + +export const isActiveWalletBeingRestored = async (client: WebdriverClient) => { + const result = await client.execute( + (expectedSyncTag: string) => { + if ( + daedalus.stores.wallets.active && + daedalus.stores.wallets.active.syncState + ) { + return daedalus.stores.wallets.active.syncState.tag === expectedSyncTag; + } + return false; + }, + WalletSyncStateTags.RESTORING + ); + return result.value; +}; + +export const waitUntilWalletIsLoaded = async function(walletName: string): Promise { + let wallet = null; + const context = this; + await context.client.waitUntil(async () => { + const result = await context.client.execute( + name => daedalus.stores.wallets.getWalletByName(name), + walletName + ); + if (result.value) { + wallet = result.value; + return true; + } + return false; + }); + return wallet; +}; + +export const waitUntilWaletNamesEqual = function(walletName: string) { + const context = this; + return context.client.waitUntil(async () => { + const currentWalletName = await getNameOfActiveWalletInSidebar.call( + context + ); + return currentWalletName === walletName; + }); }; diff --git a/features/tests/e2e/steps/wallets-limit.js b/tests/wallets/e2e/steps/wallets-limit.js similarity index 88% rename from features/tests/e2e/steps/wallets-limit.js rename to tests/wallets/e2e/steps/wallets-limit.js index 03a87dd9c5..c9f73c1696 100644 --- a/features/tests/e2e/steps/wallets-limit.js +++ b/tests/wallets/e2e/steps/wallets-limit.js @@ -1,8 +1,12 @@ +// @flow import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; -import { createWallets, getWalletByName } from '../helpers/wallets-helpers'; +import { createWallets, getWalletByName } from './helpers'; import { MAX_ADA_WALLETS_COUNT } from '../../../../source/renderer/app/config/numbersConfig'; -import sidebar from '../helpers/sidebar-helpers'; +import { sidebarHelpers } from '../../../navigation/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; + +declare var daedalus: Daedalus; Given( 'I create wallets until I reach the maximum number permitted', @@ -44,7 +48,7 @@ Then( async function(state) { const isDisabled = state === 'disabled' ? 'true' : null; - sidebar.clickAddWalletButton(this.client); + sidebarHelpers.clickAddWalletButton(this.client); await this.client.waitForVisible( '.WalletAdd_buttonsContainer .BigButtonForDialogs_component' diff --git a/features/tests/e2e/steps/wallets-steps.js b/tests/wallets/e2e/steps/wallets.js similarity index 93% rename from features/tests/e2e/steps/wallets-steps.js rename to tests/wallets/e2e/steps/wallets.js index 76ec20d611..3ef814e41a 100644 --- a/features/tests/e2e/steps/wallets-steps.js +++ b/tests/wallets/e2e/steps/wallets.js @@ -1,26 +1,28 @@ +// @flow import { Given, When, Then } from 'cucumber'; import { expect } from 'chai'; import path from 'path'; import BigNumber from 'bignumber.js'; +import { DECIMAL_PLACES_IN_ADA } from '../../../../source/renderer/app/config/numbersConfig'; import { + addWalletHelpers, + importWalletHelpers, + isActiveWalletBeingRestored, createWallets, fillOutWalletSendForm, getWalletByName, waitUntilWalletIsLoaded, addOrSetWalletsForScenario, importWalletWithFunds, -} from '../helpers/wallets-helpers'; -import { waitUntilUrlEquals, navigateTo } from '../helpers/route-helpers'; -import { DECIMAL_PLACES_IN_ADA } from '../../../../source/renderer/app/config/numbersConfig'; -import sidebar from '../helpers/sidebar-helpers'; -import addWalletPage from '../helpers/add-wallet-page-helpers'; -import importWalletDialog from '../helpers/dialogs/import-wallet-dialog-helpers'; -import i18n from '../helpers/i18n-helpers'; -import { - isActiveWalletBeingRestored, - waitForActiveRestoreNotification, -} from '../helpers/notifications-helpers'; +} from './helpers'; +import { navigateTo, sidebarHelpers, waitUntilUrlEquals } from '../../../navigation/e2e/steps/helpers'; +import { waitForActiveRestoreNotification } from '../../../nodes/e2e/steps/helpers'; +import { i18nHelpers } from '../../../settings/e2e/steps/helpers'; +import type { Daedalus } from '../../../types'; +declare var daedalus: Daedalus; + +const { formatMessage } = i18nHelpers; const defaultWalletKeyFilePath = path.resolve( __dirname, '../documents/default-wallet.key' @@ -53,7 +55,6 @@ Given(/^I have a "Imported Wallet" with funds and password$/, async function() { daedalus.api.ada .updateSpendingPassword({ walletId, - oldPassword: null, newPassword: 'Secret123', }) .then(() => @@ -84,7 +85,7 @@ Given(/^I am on the "([^"]*)" wallet "([^"]*)" screen$/, async function( }); Given(/^I see the add wallet page/, function() { - return addWalletPage.waitForVisible(this.client); + return addWalletHelpers.waitForVisible(this.client); }); Given(/^I see delete wallet dialog$/, function() { @@ -103,23 +104,16 @@ Given(/^I dont see the create wallet dialog(?: anymore)?$/, function() { return this.client.waitForVisible('.WalletCreateDialog', null, true); }); -Given(/^the active wallet is "([^"]*)"$/, function(walletName) { - const wallet = getWalletByName.call(this, walletName); - this.client.execute(walletId => { - daedalus.actions.setActiveWallet.trigger({ walletId }); - }, wallet.id); -}); - When(/^I click on the create wallet button on the add wallet page/, function() { return this.waitAndClick('.WalletAdd .createWalletButton'); }); When(/^I click on the import wallet button on the add wallet page/, function() { - return addWalletPage.clickImportButton(this.client); + return addWalletHelpers.clickImportButton(this.client); }); When(/^I see the import wallet dialog$/, function() { - return importWalletDialog.waitForDialog(this.client); + return importWalletHelpers.waitForDialog(this.client); }); When(/^I select a valid wallet import key file$/, function() { @@ -148,7 +142,7 @@ When(/^I enter wallet spending password:$/, async function(table) { When( /^I click on the import wallet button in import wallet dialog$/, function() { - return importWalletDialog.clickImport(this.client); + return importWalletHelpers.clickImport(this.client); } ); @@ -429,10 +423,10 @@ When(/^I submit the delete wallet dialog$/, function() { }); When(/^I try to import the wallet with funds again$/, async function() { - await sidebar.activateCategory(this.client, { category: 'wallets' }); - await sidebar.clickAddWalletButton(this.client); - await addWalletPage.waitForVisible(this.client); - await addWalletPage.clickImportButton(this.client); + await sidebarHelpers.activateCategory(this.client, { category: 'wallets' }); + await sidebarHelpers.clickAddWalletButton(this.client); + await addWalletHelpers.waitForVisible(this.client); + await addWalletHelpers.clickImportButton(this.client); this.waitAndClick('.WalletFileImportDialog .FileUploadWidget_dropZone'); this.waitAndClick('.Dialog_actions button'); }); @@ -440,8 +434,8 @@ When(/^I try to import the wallet with funds again$/, async function() { Then( /^I see the import wallet dialog with an error that the wallet already exists$/, async function() { - return importWalletDialog.expectError(this.client, { - error: await i18n.formatMessage(this.client, { + return importWalletHelpers.expectError(this.client, { + error: await formatMessage(this.client, { id: 'api.errors.WalletAlreadyImportedError', }), }); @@ -468,7 +462,7 @@ Then(/^I should not see the delete wallet dialog anymore$/, function() { }); Then(/^I should not see the import wallet dialog anymore$/, function() { - return importWalletDialog.waitForDialog(this.client, { isHidden: true }); + return importWalletHelpers.waitForDialog(this.client, { isHidden: true }); }); Then(/^I should not see the restore wallet dialog anymore$/, function() { diff --git a/features/mnemonics-generation-and-validation.feature b/tests/wallets/unit/features/mnemonics-generation-and-validation.feature similarity index 100% rename from features/mnemonics-generation-and-validation.feature rename to tests/wallets/unit/features/mnemonics-generation-and-validation.feature diff --git a/features/spending-password-validation.feature b/tests/wallets/unit/features/spending-password-validation.feature similarity index 100% rename from features/spending-password-validation.feature rename to tests/wallets/unit/features/spending-password-validation.feature diff --git a/features/tests/unit/steps/mnemonics-steps.js b/tests/wallets/unit/steps/mnemonics.js similarity index 91% rename from features/tests/unit/steps/mnemonics-steps.js rename to tests/wallets/unit/steps/mnemonics.js index e81c01c2dd..237af4fab8 100644 --- a/features/tests/unit/steps/mnemonics-steps.js +++ b/tests/wallets/unit/steps/mnemonics.js @@ -1,3 +1,5 @@ +// @flow +import readline from 'readline'; import { Given, Then } from 'cucumber'; import { range } from 'lodash'; import { generateAccountMnemonics } from '../../../../source/renderer/app/api/utils/mnemonics'; @@ -35,8 +37,8 @@ Given( throw new Error(`"${mnemonic}" is not valid`); } numberOfTestsExecuted++; - process.stdout.clearLine(); - process.stdout.cursorTo(0); + readline.clearLine(process.stdout, 0); + readline.cursorTo(process.stdout, 0); process.stdout.write(`${numberOfTestsExecuted} mnemonics validated.`); } } diff --git a/features/tests/unit/steps/spending-password-validation-steps.js b/tests/wallets/unit/steps/spending-password.js similarity index 100% rename from features/tests/unit/steps/spending-password-validation-steps.js rename to tests/wallets/unit/steps/spending-password.js