Skip to content

Commit

Permalink
E2E testing (GaloyMoney#655)
Browse files Browse the repository at this point in the history
* feat: add mobile e2e testing webdriver

* fix: jest version mismatch

* fix: lock files

* feat: swipe and selector helpers

* feat: script to find test device and env vars

* feat: login flow and selector utils

* feat: keyboard enter helper

* chore: PR comments
  • Loading branch information
ntheile authored Oct 31, 2022
1 parent a310e8f commit 8024dbc
Show file tree
Hide file tree
Showing 30 changed files with 3,664 additions and 371 deletions.
21 changes: 0 additions & 21 deletions .detoxrc.json

This file was deleted.

3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,12 @@ app/services/lnd/generated/*
android/api-8350101450647692243-59029-b9bec84a7e5a.json
android/fastlane/report.xml
android/fastlane/metadata/android/en-US/
android/app/release/

galoy.code-workspace

.phrase.yml
ios/GoogleService-Info.plist

.yalc
yalc.lock
yalc.lock
2 changes: 2 additions & 0 deletions app/components/onboarding/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from "react"
import { StyleSheet, Text, View } from "react-native"
import { Button } from "react-native-elements"
import { testProps } from "../../../utils/testProps"
import { color } from "../../theme"
import { palette } from "../../theme/palette"
import type { ScreenType } from "../../types/jsx"
Expand Down Expand Up @@ -68,6 +69,7 @@ export const OnboardingScreen: ScreenType = ({
</View>
{action && (
<Button
{...testProps(nextTitle)}
title={nextTitle || "Next"}
onPress={action}
containerStyle={styles.buttonContainer}
Expand Down
3 changes: 2 additions & 1 deletion app/components/version/version.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { TextStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet
import type { ComponentType } from "../../types/jsx"
import type { RootStackParamList } from "../../navigation/stack-param-lists"
import { useI18nContext } from "@app/i18n/i18n-react"
import { testProps } from "../../../utils/testProps"

const styles = StyleSheet.create({
version: {
Expand Down Expand Up @@ -36,7 +37,7 @@ export const VersionComponent: ComponentType = ({ style }: { style?: TextStylePr

return (
<Pressable onPress={() => setSecretMenuCounter(secretMenuCounter + 1)}>
<Text style={[styles.version, style]}>
<Text {...testProps("Version Build Text")} style={[styles.version, style]}>
v{VersionNumber.appVersion} build {VersionNumber.buildVersion}
{"\n"}
{/* network: {Config.BITCOIN_NETWORK} TODO */}
Expand Down
4 changes: 4 additions & 0 deletions app/screens/debug-screen/debug-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useAppConfig } from "@app/hooks/use-app-config"
import { usePersistentStateContext } from "@app/store/persistent-state"
import { GALOY_INSTANCES } from "@app/config/galoy-instances"
import { useEffect } from "react"
import { testProps } from "../../../utils/testProps"

const styles = EStyleSheet.create({
button: {
Expand Down Expand Up @@ -107,6 +108,7 @@ export const DebugScreen: ScreenType = () => {
<Text>USD per 1 sat: {usdPerSat ? `$${usdPerSat}` : "No price data"}</Text>
<Text>Hermes: {String(Boolean(usingHermes))}</Text>
<ButtonGroup
{...testProps("Galoy Instance Button")}
onPress={(index) => {
setGaloyInstance(GALOY_INSTANCES[index])
logout()
Expand All @@ -119,12 +121,14 @@ export const DebugScreen: ScreenType = () => {
containerStyle={styles.container}
/>
<GaloyInput
{...testProps("Input access token")}
placeholder={"Input access token"}
value={tempToken}
onChangeText={setTempToken}
selectTextOnFocus
/>
<Button
{...testProps("Change Token Button")}
title="Change token"
style={styles.button}
onPress={async () => {
Expand Down
4 changes: 2 additions & 2 deletions app/screens/get-started-screen/get-started-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { VersionComponent } from "../../components/version"
import { RootStackParamList } from "../../navigation/stack-param-lists"
import { palette } from "../../theme/palette"
import type { ScreenType } from "../../types/jsx"
import { testProps } from "../../../utils/testProps"

import BitcoinBeachLogo from "./bitcoin-beach-logo.png"

Expand Down Expand Up @@ -72,8 +73,7 @@ export const GetStartedScreen: ScreenType = ({ navigation }: Props) => {
titleStyle={styles.buttonTitle}
onPress={() => navigation.replace("welcomeFirst")}
containerStyle={styles.buttonContainer}
testID="getStarted"
accessibilityLabel="getStarted"
{...testProps(LL.GetStartedScreen.getStarted())}
/>
</View>
</Screen>
Expand Down
2 changes: 2 additions & 0 deletions app/screens/move-money-screen/move-money-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { BottomTabNavigationProp } from "@react-navigation/bottom-tabs"
import { CompositeNavigationProp, useIsFocused } from "@react-navigation/native"
import { useI18nContext } from "@app/i18n/i18n-react"
import { StableSatsModal } from "@app/components/stablesats-modal"
import { testProps } from "../../../utils/testProps"

const styles = EStyleSheet.create({
bottom: {
Expand Down Expand Up @@ -395,6 +396,7 @@ export const MoveMoneyScreen: ScreenType = ({
</View>

<Button
{...testProps("Settings Button")}
buttonStyle={styles.topButton}
containerStyle={styles.separator}
onPress={() => navigation.navigate("settings")}
Expand Down
1 change: 0 additions & 1 deletion app/screens/receive-bitcoin-screen/receive-bitcoin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ const ReceiveBitcoinScreen = ({
{
text: LL.common.later(),
// todo: add analytics
onPress: () => console.log("Cancel/Later Pressed"),
style: "cancel",
},
{
Expand Down
17 changes: 13 additions & 4 deletions app/screens/welcome-screens/welcome-screens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import BankShop from "./cc-bank-shop-01.svg"
import MascotDollarBitcoin from "./honey-badger-money-bitcoin-01.svg"
import HoneyBadgerShovel from "./honey-badger-shovel-01.svg"
import { useI18nContext } from "@app/i18n/i18n-react"
import { testProps } from "../../../utils/testProps"

const styles = EStyleSheet.create({
$color: palette.white,
Expand Down Expand Up @@ -62,17 +63,23 @@ export const WelcomeFirstScreen: ScreenType = ({ navigation }: Props) => {
<Screen backgroundColor={palette.lightBlue} statusBar="light-content">
<OnboardingScreen Svg={MascotDollarBitcoin}>
<Text style={styles.title}>Bitcoin:</Text>
<Text style={styles.text}>{LL.WelcomeFirstScreen.care()}</Text>
<Text {...testProps(LL.WelcomeFirstScreen.care())} style={styles.text}>
{LL.WelcomeFirstScreen.care()}
</Text>
</OnboardingScreen>
</Screen>
<Screen backgroundColor={palette.lightBlue}>
<OnboardingScreen Svg={BitcoinBitcoin}>
<Text style={styles.text}>{LL.WelcomeFirstScreen.bank()}</Text>
<Text {...testProps(LL.WelcomeFirstScreen.bank())} style={styles.text}>
{LL.WelcomeFirstScreen.bank()}
</Text>
</OnboardingScreen>
</Screen>
<Screen backgroundColor={palette.lightBlue} statusBar="light-content">
<OnboardingScreen Svg={BankShop}>
<Text style={styles.text}>{LL.WelcomeFirstScreen.before()}</Text>
<Text {...testProps(LL.WelcomeFirstScreen.before())} style={styles.text}>
{LL.WelcomeFirstScreen.before()}
</Text>
</OnboardingScreen>
</Screen>
<Screen backgroundColor={palette.lightBlue} statusBar="light-content">
Expand All @@ -83,7 +90,9 @@ export const WelcomeFirstScreen: ScreenType = ({ navigation }: Props) => {
Svg={HoneyBadgerShovel}
nextTitle={LL.WelcomeFirstScreen.learnToEarn()}
>
<Text style={styles.text}>{LL.WelcomeFirstScreen.learn()}</Text>
<Text {...testProps(LL.WelcomeFirstScreen.learn())} style={styles.text}>
{LL.WelcomeFirstScreen.learn()}
</Text>
</OnboardingScreen>
</Screen>
</Swiper>
Expand Down
2 changes: 1 addition & 1 deletion app/store/persistent-state/state-migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ export const defaultPersistentState: PersistentState = {
theme: defaultTheme,
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const deserializeAndMigratePersistentState = async (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any,
): Promise<PersistentState> => {
if (Boolean(data) && data.schemaVersion in stateMigrations) {
Expand Down
23 changes: 23 additions & 0 deletions bin/get-testing-device.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# search for android device
if [ "$TEST_DEVICE_ANDROID" ]; then
echo "android exists"
else
TEST_DEVICE_ANDROID=$(adb devices -l | grep model | awk -F':' '{print $3}' | awk '{split($0,a," "); print a[1]}')
fi
echo $TEST_DEVICE_ANDROID


# search for ios simulator
if [ "$TEST_DEVICE_IOS" ]; then
echo "ios exists"
else
TEST_DEVICE_IOS=$(xcrun simctl list devices booted | grep Booted | awk -F'(' '{print $1}' )
fi
echo $TEST_DEVICE_IOS

# TODO - TO SEARCH FOR A PHYSICAL DEVICE
# xcrun xctrace list devices

export TEST_DEVICE_ANDROID=$TEST_DEVICE_ANDROID
export TEST_DEVICE_IOS=$TEST_DEVICE_IOS

42 changes: 42 additions & 0 deletions docs/e2e-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# E2E Testing

## To Test locally with Appium and Webdriver:

1. run the debug version of the app `yarn android` or `yarn ios`
2. In a new terminal run `yarn start:appium`
3. In a new terminal run `yarn test:e2e:android` or `yarn test:e2e:ios`

## To Test with Browserstack (cloud devices):

\*\* this will eventually be integrated into CI, but for now you can test locally if you have
access to browserstack.com

```
export BROWSERSTACK_USER=YOURUSER
export BROWSERSTACK_ACCESS_KEY=YOURKEY
export BROWSERSTACK_APP_ID=bs://YOURAPPID
```

run `yarn test:browserstack:android`

## Getting the Name of an Android or IOS device

There is a script in `bin/get-testing-device.sh` that will automatically get the name of the android or ios device and set the env vars `TEST_DEVICE_ANDROID` and `TEST_DEVICE_IOS`

You can also manually set the environment variable for the test device like this:

Android

```
TEST_DEVICE_ANDROID="Pixel 3 API 29" yarn test:e2e:android
```

IOS

```
TEST_DEVICE_IOS="iPhone 13" yarn test:e2e:ios
```

## Authenticated Tests

To run the authenticated tests you need to set the env variable `GALOY_TOKEN`. The e2e test will navigate to the settings/build version page and input the token
47 changes: 47 additions & 0 deletions e2e/01-welcome-screen-flow.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { i18nObject } from "../app/i18n/i18n-util"
import { loadLocale } from "../app/i18n/i18n-util.sync"
import { swipe, selector } from "./utils"

describe("Welcome Screen Flow", async () => {
loadLocale("en")
const LL = i18nObject("en")
const timeout = 30000
beforeEach(async () => {
console.info("[beforeAll]")
})
afterEach(async () => {
console.info("[afterAll] Done with testing!")
await browser.pause(1500)
})
it("loads and clicks 'Get Started button' ", async () => {
const getStartedButton = await $(selector(LL.GetStartedScreen.getStarted()))
await getStartedButton.waitForDisplayed({ timeout })
await getStartedButton.click()
expect(true).toBeTruthy()
})

it("swipes Why Should I Care?", async () => {
const caresText = await $(selector(LL.WelcomeFirstScreen.care(), "StaticText"))
await caresText.waitForDisplayed({ timeout })
await swipe()
})

it("swipes Bitcoin is designed to let you...bank", async () => {
const bankText = await $(selector(LL.WelcomeFirstScreen.bank(), "StaticText"))
await bankText.waitForDisplayed({ timeout })
await swipe()
})

it("swipes Before Bitcoin people had to...", async () => {
const beforeText = await $(selector(LL.WelcomeFirstScreen.before(), "StaticText"))
await beforeText.waitForDisplayed({ timeout })
await swipe()
})

it("clicks 'Learn to Earn' and enters the main app", async () => {
const learnButton = await $(selector(LL.WelcomeFirstScreen.learnToEarn()))
await learnButton.waitForDisplayed({ timeout })
await learnButton.click()
expect(true).toBeTruthy()
})
})
72 changes: 72 additions & 0 deletions e2e/02-login-flow.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { i18nObject } from "../app/i18n/i18n-util"
import { loadLocale } from "../app/i18n/i18n-util.sync"
import { goBack, selector, enter } from "./utils"

describe("Login Flow", async () => {
loadLocale("en")
const LL = i18nObject("en")
const timeout = 30000
beforeEach(async () => {
console.info("[beforeAll]")
})
afterEach(async () => {
console.info("[afterAll] Done with testing!")
await browser.pause(5000)
})
it("clicks Settings Icon", async () => {
const settingsButton = await $(selector("Settings Button"))
await settingsButton.waitForDisplayed({ timeout })
await settingsButton.click()
})

it("taps Build version 3 times", async () => {
const buildButton = await $(selector("Version Build Text", "StaticText"))
await buildButton.waitForDisplayed({ timeout })
await buildButton.click()
await buildButton.click()
await buildButton.click()
})

it("click staging environment", async () => {
await browser.pause(1000)
const instanceButton = await $(selector("Galoy Instance Button", "Other"))
await instanceButton.waitForDisplayed({ timeout })
const { x, y } = await instanceButton.getLocation()
const { width, height } = await instanceButton.getSize()
// calc the midpoint center because we want to click the second button - in the middle
const midpointX = width / 2 + x
const midpointY = height / 2 + y
await browser.touchAction({ action: "tap", x: midpointX, y: midpointY })
})

it("input token", async () => {
try {
const tokenInput = await $(selector("Input access token", "TextField"))
await tokenInput.waitForDisplayed({ timeout })
await tokenInput.click()
await browser.pause(500)
await tokenInput.sendKeys(process.env.GALOY_TOKEN?.split(""))
await enter(tokenInput)
} catch (e) {
// TODO this passes but throws an error on ios even tho it works
}
})

it("click change token", async () => {
const changeTokenButton = await $(selector("Change Token Button"))
await changeTokenButton.waitForDisplayed({ timeout })
await changeTokenButton.click()
})

it("click go back to settings screen", async () => {
const backButton = await $(goBack())
await backButton.waitForDisplayed({ timeout })
await backButton.click()
})

it("click go back to home screen", async () => {
const backButton = await $(goBack())
await backButton.waitForDisplayed({ timeout })
await backButton.click()
})
})
Loading

0 comments on commit 8024dbc

Please sign in to comment.