diff --git a/.circleci/config.yml b/.circleci/config.yml index d28f7dc6892..22aeef13bf6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -438,7 +438,7 @@ jobs: - run: name: Prepare build for Detox - command: yarn detox build -c ios.sim.debug + command: RN_SRC_EXT=e2e.ts yarn detox build -c ios.sim.release - run: name: Run E2E tests with io-dev-api server @@ -455,13 +455,14 @@ jobs: yarn detox build-framework-cache yarn detox test \ --loglevel verbose \ - -c ios.sim.debug \ + -c ios.sim.release \ --cleanup \ --artifacts-location /tmp/e2e_artifacts/detox/ \ --record-logs all \ --take-screenshots all \ --record-videos failing \ - --debug-synchronization 1000 + --debug-synchronization 1000 \ + --retries 3 # store detox artifacts - store_artifacts: diff --git a/.detoxrc.json b/.detoxrc.json new file mode 100644 index 00000000000..b9a80d7fa98 --- /dev/null +++ b/.detoxrc.json @@ -0,0 +1,34 @@ +{ + "testRunner": "jest", + "runnerConfig": "jest-e2e.config.js", + "apps": { + "ios.release": { + "type": "ios.app", + "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/ItaliaApp.app", + "build": "xcodebuild -workspace ios/ItaliaApp.xcworkspace -scheme ItaliaApp -configuration Release -sdk iphonesimulator -derivedDataPath ios/build" + }, + "ios.debug": { + "type": "ios.app", + "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/ItaliaApp.app", + "build": "xcodebuild -workspace ios/ItaliaApp.xcworkspace -scheme ItaliaApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build" + } + }, + "devices": { + "simulator": { + "type": "ios.simulator", + "device": { + "type": "iPhone 12 Pro Max" + } + } + }, + "configurations": { + "ios.sim.release": { + "device": "simulator", + "app": "ios.release" + }, + "ios.sim.debug": { + "device": "simulator", + "app": "ios.debug" + } + } +} diff --git a/TESTING_E2E.md b/TESTING_E2E.md index 8509464a1b2..955d93ad44c 100644 --- a/TESTING_E2E.md +++ b/TESTING_E2E.md @@ -21,7 +21,7 @@ Since Detox is installed as an NPM package, you can run every command using `yar Preparing a build to test: ``` -yarn detox build --configuration ios.sim.release +RN_SRC_EXT=e2e.ts yarn detox build --configuration ios.sim.release ``` Launching the test suite: diff --git a/e2e/environment.js b/e2e/environment.js new file mode 100644 index 00000000000..7f4fc942fc4 --- /dev/null +++ b/e2e/environment.js @@ -0,0 +1,23 @@ +const { + DetoxCircusEnvironment, + SpecReporter, + WorkerAssignReporter, +} = require('detox/runners/jest-circus'); + +class CustomDetoxEnvironment extends DetoxCircusEnvironment { + constructor(config, context) { + super(config, context); + + // Can be safely removed, if you are content with the default value (=300000ms) + this.initTimeout = 300000; + + // This takes care of generating status logs on a per-spec basis. By default, Jest only reports at file-level. + // This is strictly optional. + this.registerListeners({ + SpecReporter, + WorkerAssignReporter, + }); + } +} + +module.exports = CustomDetoxEnvironment; diff --git a/e2e/globalSetup.js b/e2e/globalSetup.js new file mode 100644 index 00000000000..ff824b1e902 --- /dev/null +++ b/e2e/globalSetup.js @@ -0,0 +1,10 @@ +const { setLocale } = require("../ts/i18n"); +const { launchAppConfig } = require("../ts/__e2e__/config"); + +beforeAll(async () => { + // custom setup + console.log("Initializing Detox"); + await device.launchApp(launchAppConfig); + // enforce IT locale because that's how the API are configured + setLocale("it"); +}); diff --git a/jest-e2e.config.js b/jest-e2e.config.js index 543f479cb69..3d9a3893496 100644 --- a/jest-e2e.config.js +++ b/jest-e2e.config.js @@ -19,12 +19,13 @@ module.exports = { babelConfig: true } }, - setupFiles: ["./jestSetup.js"], - setupFilesAfterEnv: [ - "@testing-library/jest-native/extend-expect", - "./ts/__e2e__/init.js" - ], + setupFilesAfterEnv: ["./e2e/globalSetup.js"], testMatch: ["**/__e2e__/*.e2e.ts?(x)"], forceExit: true, - verbose: true + verbose: true, + maxWorkers: 1, + testTimeout: 300000, + testEnvironment: "./e2e/environment", + testRunner: "jest-circus/runner", + reporters: ["detox/runners/jest/streamlineReporter"] }; diff --git a/metro.config.js b/metro.config.js index e2504ccecc7..b6ca471aff0 100644 --- a/metro.config.js +++ b/metro.config.js @@ -4,6 +4,9 @@ module.exports = (async () => { const { resolver: { sourceExts, assetExts } } = await getDefaultConfig(); + const withE2ESourceExts = process.env.RN_SRC_EXT + ? process.env.RN_SRC_EXT.split(",").concat(sourceExts) + : sourceExts; return { transformer: { babelTransformerPath: require.resolve("react-native-svg-transformer"), @@ -12,7 +15,7 @@ module.exports = (async () => { }, resolver: { assetExts: assetExts.filter(ext => ext !== "svg"), - sourceExts: [...sourceExts, "svg"] + sourceExts: [...withE2ESourceExts, "svg"] } }; })(); diff --git a/package.json b/package.json index c31a8d1a2cc..195bfd25562 100644 --- a/package.json +++ b/package.json @@ -227,6 +227,7 @@ "fs-extra": "^7.0.0", "italia-utils": "^4.1.1", "jest": "^26.6.3", + "jest-circus": "^26.6.3", "jest-junit": "^13.0.0", "js-yaml": "^3.13.1", "jsdoc-to-markdown": "^6.0.1", @@ -259,41 +260,6 @@ "io-ts": "1.8.5", "react-devtools-core": "4.13.0" }, - "detox": { - "test-runner": "./node_modules/.bin/jest", - "specs": "e2e", - "runner-config": "jest-e2e.config.js", - "devices": { - "simulator": { - "type": "ios.simulator", - "device": { - "type": "iPhone 12 Pro Max" - } - } - }, - "apps": { - "ios.release": { - "type": "ios.app", - "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/ItaliaApp.app", - "build": "xcodebuild -workspace ios/ItaliaApp.xcworkspace -scheme ItaliaApp -configuration Release -sdk iphonesimulator -derivedDataPath ios/build" - }, - "ios.debug": { - "type": "ios.app", - "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/ItaliaApp.app", - "build": "xcodebuild -workspace ios/ItaliaApp.xcworkspace -scheme ItaliaApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build" - } - }, - "configurations": { - "ios.sim.release": { - "device": "simulator", - "app": "ios.release" - }, - "ios.sim.debug": { - "device": "simulator", - "app": "ios.debug" - } - } - }, "react-native": { "path": "path-browserify" }, diff --git a/ts/__e2e__/init.js b/ts/__e2e__/init.js deleted file mode 100644 index e391c942c32..00000000000 --- a/ts/__e2e__/init.js +++ /dev/null @@ -1,24 +0,0 @@ -const detox = require("detox"); -const adapter = require("detox/runners/jest/adapter"); -const { setLocale } = require("../i18n"); -const { launchAppConfig } = require("./config"); -const config = require("../../package.json").detox; - -// Set the default test timeout (in milliseconds) -jest.setTimeout(5 * 60 * 1000); -jasmine.getEnv().addReporter(adapter); - -beforeAll(async () => { - // custom setup - console.log("Initializing Detox"); - await detox.init(config, { launchApp: false }); - await device.launchApp(launchAppConfig); - // enforce IT locale because that's how the API are configured - setLocale("it"); -}); - -afterAll(async () => { - // custom teardown - await adapter.afterAll(); - await detox.cleanup(); -}); diff --git a/ts/__e2e__/login.e2e.ts b/ts/__e2e__/login.e2e.ts index 80d92d282f0..6a0b1371b26 100644 --- a/ts/__e2e__/login.e2e.ts +++ b/ts/__e2e__/login.e2e.ts @@ -1,11 +1,6 @@ -import adapter from "detox/runners/jest/adapter"; import { loginWithSPID } from "./utils"; describe("User Login using SPID", () => { - beforeEach(async () => { - await adapter.beforeEach(); - }); - describe("when the user never logged in before", () => { it("should let the user log in with SPID", async () => { await loginWithSPID(); diff --git a/ts/__e2e__/messages.e2e.ts b/ts/__e2e__/messages.e2e.ts index b357c7ab29c..9f2183b0147 100644 --- a/ts/__e2e__/messages.e2e.ts +++ b/ts/__e2e__/messages.e2e.ts @@ -1,5 +1,4 @@ import { device } from "detox"; -import adapter from "detox/runners/jest/adapter"; import I18n from "../i18n"; import { e2eWaitRenderTimeout } from "./config"; @@ -7,7 +6,6 @@ import { ensureLoggedIn } from "./utils"; describe("Messages Screen", () => { beforeEach(async () => { - await adapter.beforeEach(); await device.reloadReactNative(); await ensureLoggedIn(); }); diff --git a/ts/__e2e__/payment.e2e.ts b/ts/__e2e__/payment.e2e.ts index 5dfda71f5f1..f36773ed733 100644 --- a/ts/__e2e__/payment.e2e.ts +++ b/ts/__e2e__/payment.e2e.ts @@ -1,5 +1,3 @@ -import adapter from "detox/runners/jest/adapter"; - import I18n from "../i18n"; import { formatNumberCentsToAmount } from "../utils/stringBuilder"; import { e2eWaitRenderTimeout } from "./config"; @@ -7,7 +5,6 @@ import { ensureLoggedIn } from "./utils"; describe("Payment", () => { beforeEach(async () => { - await adapter.beforeEach(); await device.reloadReactNative(); await ensureLoggedIn(); }); diff --git a/ts/features/wallet/onboarding/__e2e__/creditCardOnboarding.e2e.ts b/ts/features/wallet/onboarding/__e2e__/creditCardOnboarding.e2e.ts index b668185e4e4..ade40d7a3e4 100644 --- a/ts/features/wallet/onboarding/__e2e__/creditCardOnboarding.e2e.ts +++ b/ts/features/wallet/onboarding/__e2e__/creditCardOnboarding.e2e.ts @@ -1,11 +1,9 @@ -import adapter from "detox/runners/jest/adapter"; import { e2eWaitRenderTimeout } from "../../../../__e2e__/config"; import { ensureLoggedIn } from "../../../../__e2e__/utils"; import I18n from "../../../../i18n"; describe("Credit Card onboarding", () => { beforeEach(async () => { - await adapter.beforeEach(); await device.reloadReactNative(); await ensureLoggedIn(); }); diff --git a/ts/screens/authentication/IdpLoginScreen.tsx b/ts/screens/authentication/IdpLoginScreen.tsx index 10569748e95..8a2b63ff57d 100644 --- a/ts/screens/authentication/IdpLoginScreen.tsx +++ b/ts/screens/authentication/IdpLoginScreen.tsx @@ -21,6 +21,7 @@ import IdpCustomContextualHelpContent from "../../components/screens/IdpCustomCo import Markdown from "../../components/ui/Markdown"; import { RefreshIndicator } from "../../components/ui/RefreshIndicator"; import I18n from "../../i18n"; +import { mixpanelTrack } from "../../mixpanel"; import { idpLoginUrlChanged, loginFailure, @@ -42,8 +43,7 @@ import { } from "../../utils/login"; import { getSpidErrorCodeDescription } from "../../utils/spidErrorCode"; import { getUrlBasepath } from "../../utils/url"; -import { mixpanelTrack } from "../../mixpanel"; -import { isDevEnv } from "../../utils/environment"; +import { originSchemasWhiteList } from "./originSchemasWhiteList"; type Props = NavigationStackScreenProps & ReturnType & @@ -103,12 +103,6 @@ const styles = StyleSheet.create({ } }); -// if the app is running in dev env, add "http" to allow the dev-server usage -const originSchemasWhiteList = [ - "https://*", - "intent://*", - ...(isDevEnv ? ["http://*"] : []) -]; /** * A screen that allows the user to login with an IDP. * The IDP page is opened in a WebView diff --git a/ts/screens/authentication/originSchemasWhiteList.e2e.ts b/ts/screens/authentication/originSchemasWhiteList.e2e.ts new file mode 100644 index 00000000000..b2d44a76805 --- /dev/null +++ b/ts/screens/authentication/originSchemasWhiteList.e2e.ts @@ -0,0 +1,2 @@ +// if the app is running in e2e env, add "http" to allow the dev-server usage +export const originSchemasWhiteList = ["https://*", "intent://*", "http://*"]; diff --git a/ts/screens/authentication/originSchemasWhiteList.ts b/ts/screens/authentication/originSchemasWhiteList.ts new file mode 100644 index 00000000000..d17a326a530 --- /dev/null +++ b/ts/screens/authentication/originSchemasWhiteList.ts @@ -0,0 +1,8 @@ +import { isDevEnv } from "../../utils/environment"; + +// if the app is running in dev env, add "http" to allow the dev-server usage +export const originSchemasWhiteList = [ + "https://*", + "intent://*", + ...(isDevEnv ? ["http://*"] : []) +]; diff --git a/yarn.lock b/yarn.lock index aa3d97dcfb2..7addc29d496 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5900,6 +5900,11 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + deep-assign@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/deep-assign/-/deep-assign-3.0.0.tgz#c8e4c4d401cba25550a2f0f486a2e75bc5f219a2" @@ -8816,6 +8821,33 @@ jest-changed-files@^26.6.2: execa "^4.0.0" throat "^5.0.0" +jest-circus@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-26.6.3.tgz#3cc7ef2a6a3787e5d7bfbe2c72d83262154053e7" + integrity sha512-ACrpWZGcQMpbv13XbzRzpytEJlilP/Su0JtNCi5r/xLpOUhnaIJr8leYYpLEMgPFURZISEHrnnpmB54Q/UziPw== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/babel__traverse" "^7.0.4" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^26.6.2" + is-generator-fn "^2.0.0" + jest-each "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" + stack-utils "^2.0.2" + throat "^5.0.0" + jest-cli@^26.6.3: version "26.6.3" resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a"