From fb3c9b0683c0efe9992d8675b0f2128e8f28b78d Mon Sep 17 00:00:00 2001 From: Nicolas Burtey Date: Fri, 14 Jul 2023 12:23:05 +0100 Subject: [PATCH 1/9] test: improve email testing --- .../email-registration-initiate.tsx | 2 +- .../email-registration-validate.tsx | 6 ++- e2e/02-login-flow.e2e.spec.ts | 36 +++++++------ e2e/utils/email.ts | 50 ++++++++++++++----- e2e/utils/graphql.ts | 2 - 5 files changed, 65 insertions(+), 31 deletions(-) diff --git a/app/screens/email-registration-screen/email-registration-initiate.tsx b/app/screens/email-registration-screen/email-registration-initiate.tsx index 29585ad8c5..7dfaf026dc 100644 --- a/app/screens/email-registration-screen/email-registration-initiate.tsx +++ b/app/screens/email-registration-screen/email-registration-initiate.tsx @@ -134,8 +134,8 @@ export const EmailRegistrationInitiateScreen: React.FC = () => { ({ screenStyle: { @@ -70,6 +71,8 @@ type EmailRegistrationValidateScreenProps = { route: RouteProp } +const placeholder = "000000" + export const EmailRegistrationValidateScreen: React.FC< EmailRegistrationValidateScreenProps > = ({ route }) => { @@ -151,7 +154,8 @@ export const EmailRegistrationValidateScreen: React.FC< characters.charAt(Math.floor(Math.random() * charactersLength))) - .join("") - return `galoy-${randomPart}` -} - describe("Login Flow", () => { loadLocale("en") const LL = i18nObject("en") const timeout = 30000 - const emailHandle = createRandomHandle() + let email = "" + let inboxId = "" it("clicks Settings Icon", async () => { await clickIcon("menu") @@ -99,7 +93,10 @@ describe("Login Flow", () => { it("adding an email", async () => { await clickOnSetting(LL.AccountScreen.emailAuthentication()) - const email = `${emailHandle}@mailinator.com` + const inboxRes = await getInbox() + if (!inboxRes) throw new Error("No inbox response") + inboxId = inboxRes.id + email = inboxRes.emailAddress const emailInput = await $( selector(LL.EmailRegistrationInitiateScreen.placeholder(), "Other", "[1]"), @@ -111,10 +108,19 @@ describe("Login Flow", () => { }) it("verifying email", async () => { - // TODO - // const code = "123456" - await clickBackButton() - await clickBackButton() + const emailRes = await getFirstEmail(inboxId) + if (!emailRes) throw new Error("No email response") + + const { subject, body } = emailRes + expect(subject).toEqual("your code") + + const code = body.split("code:\n\n")[1].slice(0, 6) + + const placeholder = "000000" + const codeInput = await $(selector(placeholder, "Other", "[1]")) + await codeInput.waitForDisplayed({ timeout }) + await codeInput.click() + await codeInput.setValue(code) }) it("navigates back to move home screen", async () => { diff --git a/e2e/utils/email.ts b/e2e/utils/email.ts index a08a0c7607..2ad22b5874 100644 --- a/e2e/utils/email.ts +++ b/e2e/utils/email.ts @@ -1,20 +1,46 @@ import axios from "axios" -import { mailinatorToken } from "./graphql" -export const fetchEmail = async (username: string) => { - const url = `https://www.mailinator.com/v2/fetch/inbox?to=${username}` - const options = { - headers: { - Authorization: mailinatorToken, - }, +export const mailslurpApiKey = process.env.MAILSLURP_API_KEY || "" + +const headers = { + "Accept": "application/json", + "x-api-key": mailslurpApiKey, +} + +export const getInbox = async () => { + const optionsCreateInbox = { + method: "POST", + url: `https://api.mailslurp.com/inboxes?useShortAddress=true`, + // url: `https://api.mailslurp.com/inboxes?expiresIn=60000&useShortAddress=true`, + headers, } + try { - const response = await axios.get(url, options) - const data = response.data - console.log(data) + const { data } = await axios.request(optionsCreateInbox) + const { id, emailAddress } = data + console.log({ id, emailAddress }) + return { id, emailAddress } + } catch (error) { + console.error(error) + } +} + +export const getFirstEmail = async (inboxId: string) => { + const optionsGetEmails = { + method: "GET", + url: `https://api.mailslurp.com/waitForNthEmail?inboxId=${inboxId}&index=0&unreadOnly=false`, + headers, + } - // Parse your code from the email here + try { + const { data } = await axios.request(optionsGetEmails) + const { subject, body } = data + console.log({ subject, body }) + return { subject, body } } catch (error) { - console.error("Error:", error) + console.error(error) } } + +// getInbox() +// getFirstEmail("a96cfd50-4c3e-4a1e-ba48-b7aa7958363f") diff --git a/e2e/utils/graphql.ts b/e2e/utils/graphql.ts index b66f1f014b..1c1b755d09 100644 --- a/e2e/utils/graphql.ts +++ b/e2e/utils/graphql.ts @@ -76,8 +76,6 @@ if (authTokens === undefined) { process.exit(1) } -export const mailinatorToken = process.env.MAILINATOR_TOKEN || "" - export const userToken = getRandomToken(authTokens) const receiverToken = process.env.GALOY_TOKEN_2 || "" From 77fcaeeb78648b6c0f8db78489b33d84507278d3 Mon Sep 17 00:00:00 2001 From: Nicolas Burtey Date: Fri, 14 Jul 2023 13:56:13 +0100 Subject: [PATCH 2/9] chore: make email verification pass --- app/i18n/en/index.ts | 2 +- .../settings-screen/account-screen.tsx | 1 + e2e/02-login-flow.e2e.spec.ts | 11 ++++++++- e2e/config/wdio.conf.js | 23 +++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/app/i18n/en/index.ts b/app/i18n/en/index.ts index 65f7c24e47..8a7ba3c262 100644 --- a/app/i18n/en/index.ts +++ b/app/i18n/en/index.ts @@ -670,7 +670,7 @@ const en: BaseTranslation = { removePhone: "Remove phone", removeEmail: "Remove email", unverified: " - Unverified", - emailUnverified: "Your email is not verified", + emailUnverified: "Your email is unverified", emailUnverifiedContent: "Ensure you can log back into your account by verifying your email. Do you want to do the verification now?", }, DefaultWalletScreen: { diff --git a/app/screens/settings-screen/account-screen.tsx b/app/screens/settings-screen/account-screen.tsx index d0f61cdb6e..b7fc4594db 100644 --- a/app/screens/settings-screen/account-screen.tsx +++ b/app/screens/settings-screen/account-screen.tsx @@ -347,6 +347,7 @@ export const AccountScreen = () => { LL.AccountScreen.emailUnverified(), LL.AccountScreen.emailUnverifiedContent(), [ + { text: LL.common.cancel(), onPress: () => {} }, { text: LL.common.ok(), onPress: () => tryConfirmEmailAgain(email), diff --git a/e2e/02-login-flow.e2e.spec.ts b/e2e/02-login-flow.e2e.spec.ts index f81f23f995..a88268b293 100644 --- a/e2e/02-login-flow.e2e.spec.ts +++ b/e2e/02-login-flow.e2e.spec.ts @@ -114,7 +114,10 @@ describe("Login Flow", () => { const { subject, body } = emailRes expect(subject).toEqual("your code") - const code = body.split("code:\n\n")[1].slice(0, 6) + const regex = /\b\d{6}\b/ + const match = body.match(regex) + if (!match) throw new Error("No code found in email body") + const code = match[0] const placeholder = "000000" const codeInput = await $(selector(placeholder, "Other", "[1]")) @@ -123,6 +126,12 @@ describe("Login Flow", () => { await codeInput.setValue(code) }) + it("clicks OK in alert dialog", async () => { + const okButton = await $(selector(LL.common.ok(), "Button")) + await okButton.waitForDisplayed({ timeout }) + await okButton.click() + }) + it("navigates back to move home screen", async () => { await clickBackButton() await waitTillSettingDisplayed(LL.common.account()) diff --git a/e2e/config/wdio.conf.js b/e2e/config/wdio.conf.js index 8474d4d808..a327a46eb8 100644 --- a/e2e/config/wdio.conf.js +++ b/e2e/config/wdio.conf.js @@ -1,3 +1,26 @@ +const androidValueForAppiumInspector = { + "platformName": "Android", + "appium:deviceName": "generic_x86", + "appium:app": "./android/app/build/outputs/apk/debug/app-universal-debug.apk", + "appium:automationName": "UiAutomator2", + "appium:snapshotMaxDepth": 500, + "appium:autoGrantPermissions": true, +} + +const iOSValueForAppiumInspector = { + "platformName": "iOS", + "appium:deviceName": "iPhone 14", + "appium:bundleId": "io.galoy.bitcoinbeach", + "appium:automationName": "XCUITest", + "appium:snapshotMaxDepth": 500, + "appium:autoAcceptAlerts": true, +} + +// value to copy to appium inspector +// to launch either android or ios +androidValueForAppiumInspector +iOSValueForAppiumInspector + let capabilities = { "platformName": "Android", "appium:deviceName": process.env.TEST_DEVICE_ANDROID || "generic_x86", From 6e4fd09c50a42ca4098e6974a4888a5790e53aba Mon Sep 17 00:00:00 2001 From: Nicolas Burtey Date: Fri, 14 Jul 2023 13:59:47 +0100 Subject: [PATCH 3/9] chore: exiting if variable env is not set --- e2e/utils/email.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/e2e/utils/email.ts b/e2e/utils/email.ts index 2ad22b5874..f0135b108f 100644 --- a/e2e/utils/email.ts +++ b/e2e/utils/email.ts @@ -1,6 +1,12 @@ import axios from "axios" -export const mailslurpApiKey = process.env.MAILSLURP_API_KEY || "" +export const mailslurpApiKey = process.env.MAILSLURP_API_KEY +if (mailslurpApiKey === undefined) { + console.error("-----------------------------") + console.error("MAILSLURP_API_KEY not set") + console.error("-----------------------------") + process.exit(1) +} const headers = { "Accept": "application/json", @@ -10,8 +16,7 @@ const headers = { export const getInbox = async () => { const optionsCreateInbox = { method: "POST", - url: `https://api.mailslurp.com/inboxes?useShortAddress=true`, - // url: `https://api.mailslurp.com/inboxes?expiresIn=60000&useShortAddress=true`, + url: `https://api.mailslurp.com/inboxes?expiresIn=60000&useShortAddress=true`, headers, } @@ -25,10 +30,10 @@ export const getInbox = async () => { } } -export const getFirstEmail = async (inboxId: string) => { +const getEmail = async (inboxId: string, index: number) => { const optionsGetEmails = { method: "GET", - url: `https://api.mailslurp.com/waitForNthEmail?inboxId=${inboxId}&index=0&unreadOnly=false`, + url: `https://api.mailslurp.com/waitForNthEmail?inboxId=${inboxId}&index=${index}&unreadOnly=false`, headers, } @@ -42,5 +47,12 @@ export const getFirstEmail = async (inboxId: string) => { } } +export const getFirstEmail = async (inboxId: string) => { + return getEmail(inboxId, 0) +} +export const getSecondEmail = async (inboxId: string) => { + return getEmail(inboxId, 1) +} + // getInbox() // getFirstEmail("a96cfd50-4c3e-4a1e-ba48-b7aa7958363f") From f704a93cc77d2b73cc8f46ea34c7414a9ee85e24 Mon Sep 17 00:00:00 2001 From: Nicolas Burtey Date: Fri, 14 Jul 2023 15:19:41 +0100 Subject: [PATCH 4/9] fix: env var import? --- ci/tasks/e2e-test-android.sh | 2 +- ci/tasks/e2e-test-ios.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/tasks/e2e-test-android.sh b/ci/tasks/e2e-test-android.sh index b2baa10ffd..5fcd8ab8f9 100755 --- a/ci/tasks/e2e-test-android.sh +++ b/ci/tasks/e2e-test-android.sh @@ -32,7 +32,7 @@ pushd repo yarn install echo browserstack_app_id:$BROWSERSTACK_APP_ID -GALOY_TEST_TOKENS=$GALOY_TEST_TOKENS && GALOY_TOKEN_2=$GALOY_TOKEN_2 && yarn test:browserstack:android | tee browserstack_output.log +GALOY_TEST_TOKENS=$GALOY_TEST_TOKENS && GALOY_TOKEN_2=$GALOY_TOKEN_2 && MAILSLURP_API_KEY=$MAILSLURP_API_KEY && yarn test:browserstack:android | tee browserstack_output.log error_code=$? SESSION_ID=$(cat browserstack_output.log | grep sessionId | head -n1 | sed -n "s/^.*'\(.*\)'.*$/\1/ p") echo "Session ID" diff --git a/ci/tasks/e2e-test-ios.sh b/ci/tasks/e2e-test-ios.sh index 12c38217a3..85d4412d62 100755 --- a/ci/tasks/e2e-test-ios.sh +++ b/ci/tasks/e2e-test-ios.sh @@ -33,7 +33,7 @@ pushd repo yarn install echo browserstack_app_id:$BROWSERSTACK_APP_ID -GALOY_TEST_TOKENS=$GALOY_TEST_TOKENS && GALOY_TOKEN_2=$GALOY_TOKEN_2 && yarn test:browserstack:ios | tee browserstack_output.log +GALOY_TEST_TOKENS=$GALOY_TEST_TOKENS && GALOY_TOKEN_2=$GALOY_TOKEN_2 && MAILSLURP_API_KEY=$MAILSLURP_API_KEY && yarn test:browserstack:ios | tee browserstack_output.log error_code=$? SESSION_ID=$(cat browserstack_output.log | grep sessionId | head -n1 | sed -n "s/^.*'\(.*\)'.*$/\1/ p") echo "Session ID" From 213224ada5e0928d9a62f5d812ce1d4762e75908 Mon Sep 17 00:00:00 2001 From: Nicolas Burtey Date: Fri, 14 Jul 2023 16:07:37 +0100 Subject: [PATCH 5/9] chore: wip tests --- .storybook/storybook.requires.js | 2 +- app/i18n/en/index.ts | 6 +-- app/i18n/i18n-types.ts | 20 +++---- app/i18n/raw-i18n/source/en.json | 8 +-- app/navigation/root-navigator.tsx | 16 +++--- app/navigation/stack-param-lists.ts | 4 +- .../email-login-flow.stories.tsx | 6 +-- ...gin-input.tsx => email-login-initiate.tsx} | 18 ++++--- ...s.tsx => email-login-validate.stories.tsx} | 14 ++--- ...alidation.tsx => email-login-validate.tsx} | 16 +++--- app/screens/email-login-screen/index.ts | 4 +- .../get-started-screen/get-started-screen.tsx | 11 ++-- e2e/02-login-flow.e2e.spec.ts | 52 ++++++++++++++++--- e2e/config/wdio.conf.js | 10 ++-- e2e/utils/email.sh | 6 +++ e2e/utils/email.ts | 4 +- 16 files changed, 127 insertions(+), 70 deletions(-) rename app/screens/email-login-screen/{email-login-input.tsx => email-login-initiate.tsx} (87%) rename app/screens/email-login-screen/{email-login-validation.stories.tsx => email-login-validate.stories.tsx} (61%) rename app/screens/email-login-screen/{email-login-validation.tsx => email-login-validate.tsx} (91%) create mode 100644 e2e/utils/email.sh diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js index 5ed706010c..2e3608a206 100644 --- a/.storybook/storybook.requires.js +++ b/.storybook/storybook.requires.js @@ -69,7 +69,7 @@ const getStories = () => { "./app/screens/earns-screen/earns-sections.stories.tsx": require("../app/screens/earns-screen/earns-sections.stories.tsx"), "./app/screens/earns-screen/section-completed.stories.tsx": require("../app/screens/earns-screen/section-completed.stories.tsx"), "./app/screens/email-login-screen/email-login-flow.stories.tsx": require("../app/screens/email-login-screen/email-login-flow.stories.tsx"), - "./app/screens/email-login-screen/email-login-validation.stories.tsx": require("../app/screens/email-login-screen/email-login-validation.stories.tsx"), + "./app/screens/email-login-screen/email-login-validate.stories.tsx": require("../app/screens/email-login-screen/email-login-validate.stories.tsx"), "./app/screens/email-registration-screen/email-registration-initiate.stories.tsx": require("../app/screens/email-registration-screen/email-registration-initiate.stories.tsx"), "./app/screens/email-registration-screen/email-registration-validate.stories.tsx": require("../app/screens/email-registration-screen/email-registration-validate.stories.tsx"), "./app/screens/error-screen/error-screen.stories.tsx": require("../app/screens/error-screen/error-screen.stories.tsx"), diff --git a/app/i18n/en/index.ts b/app/i18n/en/index.ts index 8a7ba3c262..b8503a170b 100644 --- a/app/i18n/en/index.ts +++ b/app/i18n/en/index.ts @@ -429,7 +429,7 @@ const en: BaseTranslation = { logInCreateAccount: "Log in / create account", createAccount: "Create new account", exploreWallet: "Explore wallet", - loginBackInWith: "Login back in with", + logBackInWith: "Log back in with", headline: "Wallet powered by Galoy", startTrialAccount: "Start with a trial account", iUnderstand: "I understand, continue", @@ -796,13 +796,13 @@ const en: BaseTranslation = { header: "To confirm your email address, enter the code we just sent you on {email: string}", success: "Email {email: string} confirmed successfully", }, - EmailLoginInputScreen: { + EmailLoginInitiateScreen: { title: "Login via email", header: "Enter your email address, and we'll send you an access code.", invalidEmail: "Invalid email address. Are you sure you entered the right email?", send: "Send code", }, - EmailLoginValidationScreen: { + EmailLoginValidateScreen: { title: "Code confirmation", header: "If there is an account attached to {email: string}, you should have received 6 digits code to enter below.\n\nIf you are not receiving anything, it's probably either because this is not the right email, the email is in your spam folder.", success: "Email {email: string} confirmed successfully", diff --git a/app/i18n/i18n-types.ts b/app/i18n/i18n-types.ts index 18ee1a8fb4..22d6fe0312 100644 --- a/app/i18n/i18n-types.ts +++ b/app/i18n/i18n-types.ts @@ -1397,9 +1397,9 @@ type RootTranslation = { */ exploreWallet: string /** - * L​o​g​i​n​ ​b​a​c​k​ ​i​n​ ​w​i​t​h + * L​o​g​ ​b​a​c​k​ ​i​n​ ​w​i​t​h */ - loginBackInWith: string + logBackInWith: string /** * W​a​l​l​e​t​ ​p​o​w​e​r​e​d​ ​b​y​ ​G​a​l​o​y */ @@ -2282,7 +2282,7 @@ type RootTranslation = { */ unverified: string /** - * Y​o​u​r​ ​e​m​a​i​l​ ​i​s​ ​n​o​t​ ​v​e​r​i​f​i​e​d + * Y​o​u​r​ ​e​m​a​i​l​ ​i​s​ ​u​n​v​e​r​i​f​i​e​d */ emailUnverified: string /** @@ -2684,7 +2684,7 @@ type RootTranslation = { */ success: RequiredParams<'email'> } - EmailLoginInputScreen: { + EmailLoginInitiateScreen: { /** * L​o​g​i​n​ ​v​i​a​ ​e​m​a​i​l */ @@ -2702,7 +2702,7 @@ type RootTranslation = { */ send: string } - EmailLoginValidationScreen: { + EmailLoginValidateScreen: { /** * C​o​d​e​ ​c​o​n​f​i​r​m​a​t​i​o​n */ @@ -4661,9 +4661,9 @@ export type TranslationFunctions = { */ exploreWallet: () => LocalizedString /** - * Login back in with + * Log back in with */ - loginBackInWith: () => LocalizedString + logBackInWith: () => LocalizedString /** * Wallet powered by Galoy */ @@ -5509,7 +5509,7 @@ export type TranslationFunctions = { */ unverified: () => LocalizedString /** - * Your email is not verified + * Your email is unverified */ emailUnverified: () => LocalizedString /** @@ -5901,7 +5901,7 @@ export type TranslationFunctions = { */ success: (arg: { email: string }) => LocalizedString } - EmailLoginInputScreen: { + EmailLoginInitiateScreen: { /** * Login via email */ @@ -5919,7 +5919,7 @@ export type TranslationFunctions = { */ send: () => LocalizedString } - EmailLoginValidationScreen: { + EmailLoginValidateScreen: { /** * Code confirmation */ diff --git a/app/i18n/raw-i18n/source/en.json b/app/i18n/raw-i18n/source/en.json index 3636a9d33b..cf9ec2322a 100644 --- a/app/i18n/raw-i18n/source/en.json +++ b/app/i18n/raw-i18n/source/en.json @@ -429,7 +429,7 @@ "logInCreateAccount": "Log in / create account", "createAccount": "Create new account", "exploreWallet": "Explore wallet", - "loginBackInWith": "Login back in with", + "logBackInWith": "Log back in with", "headline": "Wallet powered by Galoy", "startTrialAccount": "Start with a trial account", "iUnderstand": "I understand, continue", @@ -659,7 +659,7 @@ "removePhone": "Remove phone", "removeEmail": "Remove email", "unverified": " - Unverified", - "emailUnverified": "Your email is not verified", + "emailUnverified": "Your email is unverified", "emailUnverifiedContent": "Ensure you can log back into your account by verifying your email. Do you want to do the verification now?" }, "DefaultWalletScreen": { @@ -782,13 +782,13 @@ "header": "To confirm your email address, enter the code we just sent you on {email: string}", "success": "Email {email: string} confirmed successfully" }, - "EmailLoginInputScreen": { + "EmailLoginInitiateScreen": { "title": "Login via email", "header": "Enter your email address, and we'll send you an access code.", "invalidEmail": "Invalid email address. Are you sure you entered the right email?", "send": "Send code" }, - "EmailLoginValidationScreen": { + "EmailLoginValidateScreen": { "title": "Code confirmation", "header": "If there is an account attached to {email: string}, you should have received 6 digits code to enter below.\n\nIf you are not receiving anything, it's probably either because this is not the right email, the email is in your spam folder.", "success": "Email {email: string} confirmed successfully" diff --git a/app/navigation/root-navigator.tsx b/app/navigation/root-navigator.tsx index 834d1aec0e..5d3c4af11f 100644 --- a/app/navigation/root-navigator.tsx +++ b/app/navigation/root-navigator.tsx @@ -62,8 +62,8 @@ import { EmailRegistrationValidateScreen, } from "@app/screens/email-registration-screen" import { - EmailLoginInputScreen, - EmailLoginValidationScreen, + EmailLoginInitiateScreen, + EmailLoginValidateScreen, } from "@app/screens/email-login-screen" const useStyles = makeStyles(({ colors }) => ({ @@ -337,19 +337,19 @@ export const RootStack = () => { }} /> diff --git a/app/navigation/stack-param-lists.ts b/app/navigation/stack-param-lists.ts index 4492eccf23..3ed6b38653 100644 --- a/app/navigation/stack-param-lists.ts +++ b/app/navigation/stack-param-lists.ts @@ -79,8 +79,8 @@ export type RootStackParamList = { transactionLimitsScreen: undefined emailRegistrationInitiate: undefined emailRegistrationValidate: { email: string; emailRegistrationId: string } - emailLoginInput: undefined - emailLoginValidation: { email: string; emailLoginId: string } + emailLoginInitiate: undefined + emailLoginValidate: { email: string; emailLoginId: string } } export type ContactStackParamList = { diff --git a/app/screens/email-login-screen/email-login-flow.stories.tsx b/app/screens/email-login-screen/email-login-flow.stories.tsx index 3ec4a08c59..39c687163a 100644 --- a/app/screens/email-login-screen/email-login-flow.stories.tsx +++ b/app/screens/email-login-screen/email-login-flow.stories.tsx @@ -7,7 +7,7 @@ import { CaptchaRequestAuthCodeDocument, UserEmailRegistrationInitiateDocument, } from "../../graphql/generated" -import { EmailLoginInputScreen } from "./email-login-input" +import { EmailLoginInitiateScreen } from "./email-login-initiate" const mocks = [ { @@ -79,7 +79,7 @@ const mocks = [ export default { title: "EmailLoginFlow", - component: EmailLoginInputScreen, + component: EmailLoginInitiateScreen, decorators: [ (Story) => ( @@ -89,4 +89,4 @@ export default { ], } -export const Default = () => +export const Default = () => diff --git a/app/screens/email-login-screen/email-login-input.tsx b/app/screens/email-login-screen/email-login-initiate.tsx similarity index 87% rename from app/screens/email-login-screen/email-login-input.tsx rename to app/screens/email-login-screen/email-login-initiate.tsx index 12326616e3..473b8f489e 100644 --- a/app/screens/email-login-screen/email-login-input.tsx +++ b/app/screens/email-login-screen/email-login-initiate.tsx @@ -11,8 +11,8 @@ import { View } from "react-native" import validator from "validator" import { Screen } from "../../components/screen" import { useAppConfig } from "@app/hooks" +import { testProps } from "@app/utils/testProps" -const PLACEHOLDER_EMAIL = "hal@finney.org" const useStyles = makeStyles(({ colors }) => ({ screenStyle: { padding: 20, @@ -47,7 +47,7 @@ const useStyles = makeStyles(({ colors }) => ({ }, })) -export const EmailLoginInputScreen: React.FC = () => { +export const EmailLoginInitiateScreen: React.FC = () => { const styles = useStyles() const { appConfig: { @@ -58,7 +58,7 @@ export const EmailLoginInputScreen: React.FC = () => { const urlEmailCodeRequest = `${authUrl}/auth/email/code` const navigation = - useNavigation>() + useNavigation>() const [emailInput, setEmailInput] = React.useState("") const [errorMessage, setErrorMessage] = React.useState("") @@ -73,7 +73,7 @@ export const EmailLoginInputScreen: React.FC = () => { const submit = async () => { if (!validator.isEmail(emailInput)) { - setErrorMessage(LL.EmailLoginInputScreen.invalidEmail()) + setErrorMessage(LL.EmailLoginInitiateScreen.invalidEmail()) return } @@ -92,7 +92,8 @@ export const EmailLoginInputScreen: React.FC = () => { const emailLoginId = res.data.result if (emailLoginId) { - navigation.navigate("emailLoginValidation", { emailLoginId, email: emailInput }) + console.log({ emailLoginId }) + navigation.navigate("emailLoginValidate", { emailLoginId, email: emailInput }) } else { console.warn("no flow returned") } @@ -124,13 +125,14 @@ export const EmailLoginInputScreen: React.FC = () => { > - {LL.EmailLoginInputScreen.header()} + {LL.EmailLoginInitiateScreen.header()} { ( @@ -26,6 +26,6 @@ export default { ), ], -} as Meta +} as Meta -export const Main = () => +export const Main = () => diff --git a/app/screens/email-login-screen/email-login-validation.tsx b/app/screens/email-login-screen/email-login-validate.tsx similarity index 91% rename from app/screens/email-login-screen/email-login-validation.tsx rename to app/screens/email-login-screen/email-login-validate.tsx index 0a2af6a1de..e10b99c1a9 100644 --- a/app/screens/email-login-screen/email-login-validation.tsx +++ b/app/screens/email-login-screen/email-login-validate.tsx @@ -11,6 +11,7 @@ import { useCallback, useState } from "react" import { ActivityIndicator, View } from "react-native" import { Screen } from "../../components/screen" import analytics from "@react-native-firebase/analytics" +import { testProps } from "@app/utils/testProps" const useStyles = makeStyles(({ colors }) => ({ screenStyle: { @@ -50,17 +51,19 @@ const useStyles = makeStyles(({ colors }) => ({ }, })) -type EmailLoginValidationScreenProps = { - route: RouteProp +type EmailLoginValidateScreenProps = { + route: RouteProp } -export const EmailLoginValidationScreen: React.FC = ({ +const placeholder = "000000" + +export const EmailLoginValidateScreen: React.FC = ({ route, }) => { const styles = useStyles() const { colors } = useTheme() const navigation = - useNavigation>() + useNavigation>() const [errorMessage, setErrorMessage] = React.useState("") @@ -147,11 +150,12 @@ export const EmailLoginValidationScreen: React.FC - {LL.EmailLoginValidationScreen.header({ email })} + {LL.EmailLoginValidateScreen.header({ email })} { const navigation = @@ -76,7 +77,7 @@ export const GetStartedScreen: React.FC = () => { createDeviceAccountEnabled: Boolean(appCheckToken), }) - navigation.navigate("emailLoginInput") + navigation.navigate("emailLoginInitiate") } return ( @@ -110,14 +111,18 @@ export const GetStartedScreen: React.FC = () => { /> )} - {LL.GetStartedScreen.loginBackInWith()} + {LL.GetStartedScreen.logBackInWith()} {LL.common.phone()} {LL.common.or()} - + {LL.common.email()} diff --git a/e2e/02-login-flow.e2e.spec.ts b/e2e/02-login-flow.e2e.spec.ts index a88268b293..05b9a24a51 100644 --- a/e2e/02-login-flow.e2e.spec.ts +++ b/e2e/02-login-flow.e2e.spec.ts @@ -1,10 +1,10 @@ +import { sleep } from "../app/utils/sleep" import { i18nObject } from "../app/i18n/i18n-util" import { loadLocale } from "../app/i18n/i18n-util.sync" import { clickBackButton, clickIcon, clickOnSetting, - waitTillOnHomeScreen, waitTillSettingDisplayed, userToken, selector, @@ -15,6 +15,7 @@ import { waitTillButtonDisplayed, getInbox, getFirstEmail, + getSecondEmail, } from "./utils" describe("Login Flow", () => { @@ -124,19 +125,56 @@ describe("Login Flow", () => { await codeInput.waitForDisplayed({ timeout }) await codeInput.click() await codeInput.setValue(code) + + const okButton = await $(selector(LL.common.ok(), "Button")) + await okButton.waitForDisplayed({ timeout }) + await okButton.click() }) - it("clicks OK in alert dialog", async () => { + it("log out", async () => { + await clickOnSetting(LL.AccountScreen.logOutAndDeleteLocalData()) + + await sleep(1000) + + const iUnderstandButton = await $(selector(LL.AccountScreen.IUnderstand(), "Button")) + await iUnderstandButton.waitForDisplayed({ timeout }) + await iUnderstandButton.click() + + await sleep(1000) + const okButton = await $(selector(LL.common.ok(), "Button")) await okButton.waitForDisplayed({ timeout }) await okButton.click() }) - it("navigates back to move home screen", async () => { - await clickBackButton() - await waitTillSettingDisplayed(LL.common.account()) + it("log back in", async () => { + const emailLink = await $(selector("email-button", "Other")) + await emailLink.waitForDisplayed({ timeout }) + await emailLink.click() - await clickBackButton() - await waitTillOnHomeScreen() + const emailInput = await $( + selector(LL.EmailRegistrationInitiateScreen.placeholder(), "Other", "[1]"), + ) + await emailInput.waitForDisplayed({ timeout }) + await emailInput.click() + await emailInput.setValue(email) + await clickButton(LL.EmailRegistrationInitiateScreen.send()) + // i9TEikJakmv4@mailslurp.com + const emailRes = await getSecondEmail(inboxId) + if (!emailRes) throw new Error("No email response") + + const { subject, body } = emailRes + expect(subject).toEqual("your code") + + const regex = /\b\d{6}\b/ + const match = body.match(regex) + if (!match) throw new Error("No code found in email body") + const code = match[0] + + const placeholder = "000000" + const codeInput = await $(selector(placeholder, "Other", "[1]")) + await codeInput.waitForDisplayed({ timeout }) + await codeInput.click() + await codeInput.setValue(code) }) }) diff --git a/e2e/config/wdio.conf.js b/e2e/config/wdio.conf.js index a327a46eb8..8bbfa22325 100644 --- a/e2e/config/wdio.conf.js +++ b/e2e/config/wdio.conf.js @@ -1,10 +1,12 @@ +/* eslint-disable */ + const androidValueForAppiumInspector = { "platformName": "Android", "appium:deviceName": "generic_x86", "appium:app": "./android/app/build/outputs/apk/debug/app-universal-debug.apk", "appium:automationName": "UiAutomator2", "appium:snapshotMaxDepth": 500, - "appium:autoGrantPermissions": true, + "appium:autoGrantPermissions": false } const iOSValueForAppiumInspector = { @@ -13,7 +15,7 @@ const iOSValueForAppiumInspector = { "appium:bundleId": "io.galoy.bitcoinbeach", "appium:automationName": "XCUITest", "appium:snapshotMaxDepth": 500, - "appium:autoAcceptAlerts": true, + "appium:autoAcceptAlerts": false } // value to copy to appium inspector @@ -29,7 +31,7 @@ let capabilities = { "./android/app/build/outputs/apk/debug/app-universal-debug.apk", "appium:automationName": "UiAutomator2", "appium:snapshotMaxDepth": 500, - "appium:autoGrantPermissions": true, + "appium:autoGrantPermissions": false, } if (process.env.E2E_DEVICE === "ios") { @@ -40,7 +42,7 @@ if (process.env.E2E_DEVICE === "ios") { "appium:bundleId": "io.galoy.bitcoinbeach", "appium:automationName": "XCUITest", "appium:snapshotMaxDepth": 500, - "appium:autoAcceptAlerts": true, + "appium:autoAcceptAlerts": false, } } diff --git a/e2e/utils/email.sh b/e2e/utils/email.sh new file mode 100644 index 0000000000..07f90ff3f9 --- /dev/null +++ b/e2e/utils/email.sh @@ -0,0 +1,6 @@ +``` +curl -s -X 'GET' \ + "https://api.mailslurp.com/inboxes/$inboxId/emails" \ + -H 'accept: */*' \ + -H "x-api-key: $MAILSLURP_API_KEY" +``` diff --git a/e2e/utils/email.ts b/e2e/utils/email.ts index f0135b108f..0bdeac7161 100644 --- a/e2e/utils/email.ts +++ b/e2e/utils/email.ts @@ -16,14 +16,14 @@ const headers = { export const getInbox = async () => { const optionsCreateInbox = { method: "POST", - url: `https://api.mailslurp.com/inboxes?expiresIn=60000&useShortAddress=true`, + url: `https://api.mailslurp.com/inboxes?expiresIn=3600000&useShortAddress=true`, headers, } try { const { data } = await axios.request(optionsCreateInbox) const { id, emailAddress } = data - console.log({ id, emailAddress }) + console.log({ inboxId: id, emailAddress }) return { id, emailAddress } } catch (error) { console.error(error) From 0536ce26a8b899da2904535ddc3a9d3cff2511e3 Mon Sep 17 00:00:00 2001 From: Nicolas Burtey Date: Fri, 14 Jul 2023 19:56:52 +0100 Subject: [PATCH 6/9] chore: missing some mailslurp env variable --- android/fastlane/Fastfile | 2 +- ios/fastlane/Fastfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile index cb0b467d7d..8fd3b1f049 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -110,6 +110,6 @@ platform :android do browserstack_access_key: ENV["BROWSERSTACK_ACCESS_KEY"], file_path: ENV["GRADLE_APK_OUTPUT_PATH"] ) - sh("GALOY_TEST_TOKENS=$GALOY_TEST_TOKENS && GALOY_TOKEN_2=$GALOY_TOKEN_2 && yarn test:browserstack:android") + sh("GALOY_TEST_TOKENS=$GALOY_TEST_TOKENS && GALOY_TOKEN_2=$GALOY_TOKEN_2 && MAILSLURP_API_KEY=$MAILSLURP_API_KEY && yarn test:browserstack:android") end end diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index 2cf283e347..d3628c16f3 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -128,6 +128,6 @@ platform :ios do browserstack_username: ENV["BROWSERSTACK_USER"], browserstack_access_key: ENV["BROWSERSTACK_ACCESS_KEY"], ) - sh("GALOY_TEST_TOKENS=$GALOY_TEST_TOKENS && GALOY_TOKEN_2=$GALOY_TOKEN_2 && yarn test:browserstack:ios") + sh("GALOY_TEST_TOKENS=$GALOY_TEST_TOKENS && GALOY_TOKEN_2=$GALOY_TOKEN_2 && MAILSLURP_API_KEY=$MAILSLURP_API_KEY && yarn test:browserstack:ios") end end From 74c6901f4fe943f94c485a655adad4bb67cf362f Mon Sep 17 00:00:00 2001 From: Nicolas Burtey Date: Fri, 14 Jul 2023 21:49:00 +0100 Subject: [PATCH 7/9] test: almost making test pass --- .../get-started-screen/get-started-screen.tsx | 1 + e2e/02-login-flow.e2e.spec.ts | 26 ++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/screens/get-started-screen/get-started-screen.tsx b/app/screens/get-started-screen/get-started-screen.tsx index 53f705a14a..cd5ff32a16 100644 --- a/app/screens/get-started-screen/get-started-screen.tsx +++ b/app/screens/get-started-screen/get-started-screen.tsx @@ -85,6 +85,7 @@ export const GetStartedScreen: React.FC = () => { setSecretMenuCounter(secretMenuCounter + 1)} style={styles.logoContainer} + {...testProps("logo-button")} > diff --git a/e2e/02-login-flow.e2e.spec.ts b/e2e/02-login-flow.e2e.spec.ts index 05b9a24a51..afe91a45cd 100644 --- a/e2e/02-login-flow.e2e.spec.ts +++ b/e2e/02-login-flow.e2e.spec.ts @@ -1,4 +1,3 @@ -import { sleep } from "../app/utils/sleep" import { i18nObject } from "../app/i18n/i18n-util" import { loadLocale } from "../app/i18n/i18n-util.sync" import { @@ -134,19 +133,34 @@ describe("Login Flow", () => { it("log out", async () => { await clickOnSetting(LL.AccountScreen.logOutAndDeleteLocalData()) - await sleep(1000) - const iUnderstandButton = await $(selector(LL.AccountScreen.IUnderstand(), "Button")) await iUnderstandButton.waitForDisplayed({ timeout }) await iUnderstandButton.click() - await sleep(1000) - const okButton = await $(selector(LL.common.ok(), "Button")) await okButton.waitForDisplayed({ timeout }) await okButton.click() }) + it("set staging environment again", async () => { + const buildButton = await $(selector("logo-button", "Other")) + await buildButton.waitForDisplayed({ timeout }) + await buildButton.click() + await browser.pause(100) + await buildButton.click() + await browser.pause(100) + await buildButton.click() + await browser.pause(100) + + // scroll down for small screens + await waitTillButtonDisplayed("logout button") + await scrollDown() + await clickButton("Staging Button") + await clickButton("Save Changes") + + await clickBackButton() + }) + it("log back in", async () => { const emailLink = await $(selector("email-button", "Other")) await emailLink.waitForDisplayed({ timeout }) @@ -159,7 +173,7 @@ describe("Login Flow", () => { await emailInput.click() await emailInput.setValue(email) await clickButton(LL.EmailRegistrationInitiateScreen.send()) - // i9TEikJakmv4@mailslurp.com + const emailRes = await getSecondEmail(inboxId) if (!emailRes) throw new Error("No email response") From 07d813c42b8af15008111b4204ce26fd760a9a91 Mon Sep 17 00:00:00 2001 From: Nicolas Burtey Date: Fri, 14 Jul 2023 22:28:12 +0100 Subject: [PATCH 8/9] test: fix e2e --- e2e/02-login-flow.e2e.spec.ts | 1 + e2e/05-payments-receive-flow.e2e.spec.ts | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/e2e/02-login-flow.e2e.spec.ts b/e2e/02-login-flow.e2e.spec.ts index afe91a45cd..f6b77a9744 100644 --- a/e2e/02-login-flow.e2e.spec.ts +++ b/e2e/02-login-flow.e2e.spec.ts @@ -101,6 +101,7 @@ describe("Login Flow", () => { const emailInput = await $( selector(LL.EmailRegistrationInitiateScreen.placeholder(), "Other", "[1]"), ) + await emailInput.waitForDisplayed({ timeout }) await emailInput.click() await emailInput.setValue(email) diff --git a/e2e/05-payments-receive-flow.e2e.spec.ts b/e2e/05-payments-receive-flow.e2e.spec.ts index 08ce24eb37..1c129ba80d 100644 --- a/e2e/05-payments-receive-flow.e2e.spec.ts +++ b/e2e/05-payments-receive-flow.e2e.spec.ts @@ -28,9 +28,18 @@ describe("Receive BTC Amount Payment Flow", () => { it("Click Request Specific Amount", async () => { await clickPressable(LL.ReceiveWrapperScreen.addAmount()) + // we need to wait for the notifications permissions pop up // and click allow before we can continue await browser.pause(4000) + try { + const allowButton = await $(selector("Allow", "Button")) + if (await allowButton.isDisplayed()) { + await allowButton.click() + } + } catch (error) { + // Ignore the error if the "Allow" button isn't found + } }) it("Enter Amount", async () => { From cab408094e6050811e39647a2aff2ae2c9a8f573 Mon Sep 17 00:00:00 2001 From: Nicolas Burtey Date: Mon, 17 Jul 2023 10:22:39 +0100 Subject: [PATCH 9/9] fix: e2e test --- app/i18n/en/index.ts | 1 - app/i18n/i18n-types.ts | 8 -------- app/i18n/raw-i18n/source/en.json | 1 - docs/e2e-testing.md | 1 + e2e/02-login-flow.e2e.spec.ts | 20 ++++++++++---------- e2e/utils/controls.ts | 14 ++++++++++++++ 6 files changed, 25 insertions(+), 20 deletions(-) diff --git a/app/i18n/en/index.ts b/app/i18n/en/index.ts index b8503a170b..93338790eb 100644 --- a/app/i18n/en/index.ts +++ b/app/i18n/en/index.ts @@ -432,7 +432,6 @@ const en: BaseTranslation = { logBackInWith: "Log back in with", headline: "Wallet powered by Galoy", startTrialAccount: "Start with a trial account", - iUnderstand: "I understand, continue", startWithTrialAccount: "Start with trial account", registerPhoneAccount: "Register phone account", trialAccountCreationFailed: "Trial account creation failed", diff --git a/app/i18n/i18n-types.ts b/app/i18n/i18n-types.ts index 22d6fe0312..9b100a0bd9 100644 --- a/app/i18n/i18n-types.ts +++ b/app/i18n/i18n-types.ts @@ -1408,10 +1408,6 @@ type RootTranslation = { * S​t​a​r​t​ ​w​i​t​h​ ​a​ ​t​r​i​a​l​ ​a​c​c​o​u​n​t */ startTrialAccount: string - /** - * I​ ​u​n​d​e​r​s​t​a​n​d​,​ ​c​o​n​t​i​n​u​e - */ - iUnderstand: string /** * S​t​a​r​t​ ​w​i​t​h​ ​t​r​i​a​l​ ​a​c​c​o​u​n​t */ @@ -4672,10 +4668,6 @@ export type TranslationFunctions = { * Start with a trial account */ startTrialAccount: () => LocalizedString - /** - * I understand, continue - */ - iUnderstand: () => LocalizedString /** * Start with trial account */ diff --git a/app/i18n/raw-i18n/source/en.json b/app/i18n/raw-i18n/source/en.json index cf9ec2322a..34c30141f5 100644 --- a/app/i18n/raw-i18n/source/en.json +++ b/app/i18n/raw-i18n/source/en.json @@ -432,7 +432,6 @@ "logBackInWith": "Log back in with", "headline": "Wallet powered by Galoy", "startTrialAccount": "Start with a trial account", - "iUnderstand": "I understand, continue", "startWithTrialAccount": "Start with trial account", "registerPhoneAccount": "Register phone account", "trialAccountCreationFailed": "Trial account creation failed", diff --git a/docs/e2e-testing.md b/docs/e2e-testing.md index 3e273357cc..e9ca2a5ee3 100644 --- a/docs/e2e-testing.md +++ b/docs/e2e-testing.md @@ -71,6 +71,7 @@ Here are the other env variables you need to set ``` GALOY_TEST_TOKENS={YOUR_TOKEN} GALOY_TOKEN_2={SECOND_WALLET_TOKEN} +MAILSLURP_API_KEY={MAILSLURP_API_KEY} E2E_DEVICE={ios or android} ``` diff --git a/e2e/02-login-flow.e2e.spec.ts b/e2e/02-login-flow.e2e.spec.ts index f6b77a9744..d05dc20c1a 100644 --- a/e2e/02-login-flow.e2e.spec.ts +++ b/e2e/02-login-flow.e2e.spec.ts @@ -15,8 +15,14 @@ import { getInbox, getFirstEmail, getSecondEmail, + clickAlertLastButton, } from "./utils" +const sleep = (ms: number): Promise => + new Promise((resolve) => { + setTimeout(resolve, ms) + }) + describe("Login Flow", () => { loadLocale("en") const LL = i18nObject("en") @@ -126,21 +132,15 @@ describe("Login Flow", () => { await codeInput.click() await codeInput.setValue(code) - const okButton = await $(selector(LL.common.ok(), "Button")) - await okButton.waitForDisplayed({ timeout }) - await okButton.click() + clickAlertLastButton(LL.common.ok()) }) it("log out", async () => { await clickOnSetting(LL.AccountScreen.logOutAndDeleteLocalData()) - const iUnderstandButton = await $(selector(LL.AccountScreen.IUnderstand(), "Button")) - await iUnderstandButton.waitForDisplayed({ timeout }) - await iUnderstandButton.click() - - const okButton = await $(selector(LL.common.ok(), "Button")) - await okButton.waitForDisplayed({ timeout }) - await okButton.click() + clickAlertLastButton(LL.AccountScreen.IUnderstand()) + await sleep(250) + clickAlertLastButton(LL.common.ok()) }) it("set staging environment again", async () => { diff --git a/e2e/utils/controls.ts b/e2e/utils/controls.ts index 70a89adb21..1b25c0d493 100644 --- a/e2e/utils/controls.ts +++ b/e2e/utils/controls.ts @@ -7,6 +7,20 @@ export const selector = (id: string, iosType?: string, iosExtraXPath?: string) = return `~${id}` } +const findById = (id: string, iosType?: string, iosExtraXPath?: string) => { + if (process.env.E2E_DEVICE === "ios") { + return `//XCUIElementType${iosType}[@name="${id}"]${iosExtraXPath ?? ""}` + } + return `id=${id}` +} + +export const clickAlertLastButton = async (title: string) => { + const okButtonId = process.env.E2E_DEVICE === "ios" ? title : "android:id/button1" + const okButton = await $(findById(okButtonId, "Button")) + await okButton.waitForDisplayed({ timeout }) + await okButton.click() +} + export const clickButton = async (title: string) => { const button = await $(selector(title, "Button")) await button.waitForEnabled({ timeout })