Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

E2E tests setup and initial tests #1417

Merged
merged 30 commits into from
Sep 27, 2019
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6bfa5b6
chore(jest): make typeorm importable in tests
mnzaki Sep 7, 2019
8a44324
fix: error because of missing type in src/config.ts
mnzaki Sep 7, 2019
68618a9
chore(e2e): add and configure Detox e2e testing framework
mnzaki Aug 7, 2019
fcb1c5f
chore(e2e): auto-configure device/emulator names
mnzaki Aug 7, 2019
aa6e4d5
chore(e2e): update jest config
mnzaki Aug 13, 2019
02cacad
chore(e2e): add utils.readVisibleText
mnzaki Aug 20, 2019
3d3e1cc
chore(e2e): add some comments
mnzaki Aug 21, 2019
4c12bea
chore: update @types/react-native-material-ui
mnzaki Sep 11, 2019
7827496
chore: update detox and @types/detox
mnzaki Sep 11, 2019
bc3cd3c
chore(e2e): terminate e2e tests if detox.init fails
mnzaki Sep 11, 2019
55fa75c
chore: add fs-extra as a direct dev dependency
mnzaki Sep 12, 2019
51ac0de
chore(e2e/utils): improve readVisibleText
mnzaki Sep 12, 2019
d268196
test(landing): e2e tests for landing screen
mnzaki Sep 12, 2019
357fc71
test(creation): e2e tests for identity creation
mnzaki Sep 12, 2019
57b89e8
test(recovery): seed phrase recovery tests
mnzaki Sep 12, 2019
5a90cf1
test(interactions): add test boilerplate
mnzaki Sep 12, 2019
93eedee
Merge remote-tracking branch 'origin/develop' into 1415/e2e_tests_setup
mnzaki Sep 13, 2019
6069821
chore: update snapshots
mnzaki Sep 13, 2019
aaa5b1d
chore(e2e/utils): fix detox device access pattern
mnzaki Sep 13, 2019
cfd247b
Merge remote-tracking branch 'origin/develop' into 1415/e2e_tests_setup
mnzaki Sep 13, 2019
284cba7
chore(e2e/utils): extract getDetoxConfig to utils
mnzaki Sep 17, 2019
ce37399
test(e2e/creation): manual sychronization around hand animation
mnzaki Sep 17, 2019
2f8f1dc
chore(e2e): add detox release from mnzaki's fork
mnzaki Sep 17, 2019
92984eb
fix(registration/handAnimation): stop looping after unmount
mnzaki Sep 17, 2019
7587d59
chore: use detox fork from jolocom
mnzaki Sep 17, 2019
395897d
chore(e2e): add dummy tests to suppress failure
mnzaki Sep 17, 2019
2e5b07c
Merge remote-tracking branch 'origin/develop' into 1415/e2e_tests_setup
mnzaki Sep 26, 2019
d7c444f
chore(README): add quick note about e2e
mnzaki Sep 26, 2019
1da2f46
chore: add yarn e2e:ios:sim
mnzaki Sep 26, 2019
c739076
Merge remote-tracking branch 'origin/develop' into 1415/e2e_tests_setup
mnzaki Sep 27, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ android {
versionName "1.7.0"
vectorDrawables.useSupportLibrary = true
missingDimensionStrategy 'react-native-camera', 'general'
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
VolkerSchiewe marked this conversation as resolved.
Show resolved Hide resolved
}
splits {
abi {
Expand Down Expand Up @@ -139,6 +141,7 @@ android {
debug {
applicationIdSuffix ".debug"
debuggable true
splits.abi.enable false
}
staging {
initWith debug
Expand Down Expand Up @@ -178,6 +181,8 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation "com.facebook.react:react-native:+" // From node_modules
androidTestImplementation('com.wix:detox:+') { transitive = true }
androidTestImplementation 'junit:junit:4.12'
}

// Run this once to be able to run the application with BUCK
Expand Down
24 changes: 24 additions & 0 deletions android/app/src/androidTest/java/com/jolocomwallet/DetoxTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.jolocomwallet;

import com.wix.detox.Detox;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class DetoxTest {

@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);

@Test
public void runDetoxTests() {
Detox.runTests(mActivityRule);
}
}
11 changes: 10 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@
buildscript {
ext {
buildToolsVersion = "28.0.3"
minSdkVersion = 16
// detox requires at least version 18
minSdkVersion = 18
compileSdkVersion = 28
targetSdkVersion = 28
supportLibVersion = "28.0.0"
// detox instrumentation app uses kotlin
kotlinVersion = "1.3.0"
mnzaki marked this conversation as resolved.
Show resolved Hide resolved
}
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
// detox instrumentation app uses kotlin
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand All @@ -33,5 +38,9 @@ allprojects {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
maven {
// All of Detox' artifacts are provided via the npm module
url "$rootDir/../node_modules/detox/Detox-android"
}
}
}
20 changes: 20 additions & 0 deletions e2e/01_new_user/01_landing.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect } from 'detox'

describe('Landing', () => {
describe('Landing Screen', () => {
it('should show a landingCarousel', async () => {
const landingCarousel = element(by.id('landingCarousel'))
await expect(landingCarousel).toBeVisible()
})

it('should show a getStarted button', async () => {
const getStarted = element(by.id('getStarted'))
await expect(getStarted).toBeVisible()
})

it('should show a recoverIdentity button', async () => {
const recoverIdentity = element(by.id('recoverIdentity'))
await expect(recoverIdentity).toBeVisible()
})
})
})
59 changes: 59 additions & 0 deletions e2e/01_new_user/02_creation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { expect } from 'detox'
import jestExpect from 'expect'
import { readVisibleText } from 'e2e/utils'

describe('Identity Creation', () => {
describe('Entropy Screen', () => {
let progressRegexp = /(\d+) %/

beforeAll(async () => {
// we must disable automatic synchronization because of the inifinite loop
// animation on the entropy screen
await device.disableSynchronization()
const getStarted = element(by.id('getStarted'))
await getStarted.tap()
// and manually synchronize by waiting for entropyMessage to be visible
// NOTE: waitFor polls
await waitFor(element(by.id('entropyMsg'))).toBeVisible().withTimeout(2000)
})

it('should show an entropyMsg help text at first', async () => {
const entropyMsg = element(by.id('entropyMsg'))
await expect(entropyMsg).toBeVisible()
const text = await readVisibleText('entropyMsg')
jestExpect(text).not.toMatch(progressRegexp)
})

it('should show a percentage of entropy collected on swipe in scratchArea', async () => {
const scratchArea = element(by.id('scratchArea'))
await scratchArea.swipe('right', 'slow')

// at this point the animation is hidden
await device.enableSynchronization()

const text = await readVisibleText('entropyMsg')
jestExpect(text).toMatch(progressRegexp)

const swipeDirs: Detox.Direction[] = ['up', 'down', 'left', 'right']
for (let progress = 0; progress < 100; ) {
let text;
try {
text = await readVisibleText('entropyMsg')
} catch (err) {
// if we made enough progress then screen will have changed and
// entropyMsg will not be visible
if (progress > 90) break
else throw err
}
const match = progressRegexp.exec(text)
jestExpect(match).toHaveLength(2)
progress = parseInt((match as Array<string>)[1])
await scratchArea.swipe(swipeDirs[progress % 4], 'fast')
}
})

it('should navigate home after successful creation', async () => {
await waitFor(element(by.id('claimsScreen'))).toBeVisible().withTimeout(30000)
})
})
})
83 changes: 83 additions & 0 deletions e2e/01_new_user/03_recovery.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { expect } from 'detox'
import { readVisibleText } from 'e2e/utils';
import jestExpect from 'expect'

describe('Identity Recovery', () => {
beforeAll(async () => {
const recoverIdentity = element(by.id('recoverIdentity'))
await recoverIdentity.tap()
})

describe('Input Seed Phrase Screen', () => {
it('should show a recoveryMsg', async () => {
const recoveryMsg = element(by.id('recoveryMsg'))
await expect(recoveryMsg).toBeVisible()
})

it('should show a seedWordFld', async () => {
const seedWordFld = element(by.id('seedWordFld'))
await expect(seedWordFld).toBeVisible()
await seedWordFld.tap()
})

const seedPhraseWords = [
'school', 'leopard', 'pretty', 'shell',
'soup', 'paddle', 'spot', 'absurd',
'blame', 'morning', 'perfect', 'local',
]

it('should show word suggestions based on input prefix', async () => {
const seedWordFld = element(by.id('seedWordFld'))

for (let w = 0; w < 4; w++) {
const word = seedPhraseWords[w]

// type a part of the word and expect suggestions
const wordPrefix = word.slice(0, word.length/2)
await seedWordFld.replaceText(wordPrefix)
for (let e = 0; e < 10; e++) {
let suggestion
try {
suggestion = await readVisibleText(`seedSuggestion${e}`)
} catch (err) {
// we don't know how many suggestions there are, so just break
// TODO figure out how to count with detox
break
}
jestExpect(suggestion.slice(0, wordPrefix.length)).toMatch(wordPrefix)
}
}
})

it('should add words to seed phrase on tap', async () => {
const seedWordFld = element(by.id('seedWordFld'))
const seedPhraseMsg = element(by.id('seedPhraseMsg'))
await expect(seedPhraseMsg).toBeVisible()

for (let w = 0; w < seedPhraseWords.length; w++) {
const word = seedPhraseWords[w]
// type the full word and expect to have it as the first suggestion
// TODO test that it is the only suggestion
await seedWordFld.replaceText(word)
const suggestionBtn = element(by.id('seedSuggestion0').withDescendant(by.text(word)))
await expect(suggestionBtn).toBeVisible()
await suggestionBtn.tap()

// expect the seedPhrase displayed to be updated
const curSeedPhrase = await readVisibleText('seedPhraseMsg')
const expectedSeedPhrase = seedPhraseWords.slice(0, w+1).join('')
jestExpect(curSeedPhrase).toEqual(expectedSeedPhrase)
}
})

it('should show a restoreAccount button', async () => {
const restoreAccount = element(by.id('restoreAccount'))
await expect(restoreAccount).toBeVisible()
await restoreAccount.tap()
})

it('should navigate home after a successful restore', async () => {
await waitFor(element(by.id('claimsScreen'))).toBeVisible().withTimeout(10000)
})
})
})
6 changes: 6 additions & 0 deletions e2e/02_interactions/01_payment.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
describe('Payment', () => {
describe('Payment Screen', () => {
it('should be tested', () => {
})
})
})
6 changes: 6 additions & 0 deletions e2e/02_interactions/02_authentication.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
describe('Authentication', () => {
describe('Authentication Screen', () => {
it('should be tested', () => {
})
})
})
6 changes: 6 additions & 0 deletions e2e/02_interactions/03_credential_request.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
describe('Credential Request', () => {
describe('Credential Request Screen', () => {
it('should be tested', () => {
})
})
})
6 changes: 6 additions & 0 deletions e2e/02_interactions/04_credential_offer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
describe('Credential Offer', () => {
describe('Credential Offer Screen', () => {
it('should be tested', () => {
})
})
})
4 changes: 4 additions & 0 deletions e2e/02_interactions/05_errors.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
describe('Interaction Errors', () => {
it('should be tested', () => {
})
})
33 changes: 33 additions & 0 deletions e2e/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const detox = require('detox')
const getDetoxConfig = require('./utils').getDetoxConfig
const adapter = require('detox/runners/jest/adapter')
const specReporter = require('detox/runners/jest/specReporter')

// Set the default timeout
jest.setTimeout(120000)
jasmine.getEnv().addReporter(adapter)

// This takes care of generating status logs on a per-spec basis. By default,
// jest only reports at file-level. This is strictly optional.
jasmine.getEnv().addReporter(specReporter)

beforeAll(async () => {
try {
const detoxConfig = await getDetoxConfig()
await detox.init(detoxConfig)
} catch (err) {
// when detox init fails we should really stop the tests otherwise we get a
// bunch of unrelated and misleading errors
console.error('Detox init failed!', err)
process.exit(1)
}
});

beforeEach(async () => {
await adapter.beforeEach()
});

afterAll(async () => {
await adapter.afterAll()
await detox.cleanup()
});
41 changes: 41 additions & 0 deletions e2e/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module.exports = {
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
"verbose": true,

globals: {
"ts-jest": {
babelConfig: true,
diagnostics: {
warnOnly: true
}
}
},

moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/tests/assetsTransformer.js"
},

moduleFileExtensions: [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],

moduleDirectories: [
"<rootDir>/../node_modules",
"<rootDir>/.."
],

testPathIgnorePatterns: [
"/node_modules/.*"
],

transform: {
"^.+\\.tsx?$": "ts-jest"
}
}
Loading