diff --git a/packages/detox/README.md b/packages/detox/README.md index 679a4d93..ada86aa9 100644 --- a/packages/detox/README.md +++ b/packages/detox/README.md @@ -62,6 +62,7 @@ The plugin provides props for extra customization. Every time you change the pro - `skipProguard` (_boolean_): Disable adding proguard minification to the `app/build.gradle`. Defaults to `false`. - `subdomains` (_string[] | '\*'_): Hostnames to add to the network security config. Pass `'*'` to allow all domains. Defaults to `['10.0.2.2', 'localhost']`. +- `includeTestButler` (_boolean_): Enable [Test Butler](https://github.com/linkedin/test-butler) library injection in `app/build.grade` and modifications to JUnit test runner. Defaults to `false` `app.config.js` @@ -73,6 +74,7 @@ export default { { skipProguard: false, subdomains: ["10.0.2.2", "localhost"], + includeTestButler: false }, ], ], diff --git a/packages/detox/build/withDetox.d.ts b/packages/detox/build/withDetox.d.ts index 42da2140..56c6fa97 100644 --- a/packages/detox/build/withDetox.d.ts +++ b/packages/detox/build/withDetox.d.ts @@ -15,5 +15,11 @@ declare const _default: ConfigPlugin; export default _default; diff --git a/packages/detox/build/withDetox.js b/packages/detox/build/withDetox.js index e1611413..d4125e2d 100644 --- a/packages/detox/build/withDetox.js +++ b/packages/detox/build/withDetox.js @@ -6,16 +6,18 @@ Object.defineProperty(exports, "__esModule", { value: true }); const config_plugins_1 = require("@expo/config-plugins"); const withDetoxProjectGradle_1 = __importDefault(require("./withDetoxProjectGradle")); const withDetoxTestAppGradle_1 = __importDefault(require("./withDetoxTestAppGradle")); -const withDetoxTestClass_1 = require("./withDetoxTestClass"); +const withDetoxTestClass_1 = __importDefault(require("./withDetoxTestClass")); +const withJUnitRunnerClass_1 = __importDefault(require("./withJUnitRunnerClass")); const withKotlinGradle_1 = __importDefault(require("./withKotlinGradle")); const withNetworkSecurityConfig_1 = require("./withNetworkSecurityConfig"); const withProguardGradle_1 = __importDefault(require("./withProguardGradle")); -const withDetox = (config, { skipProguard, subdomains } = {}) => { +const withTestButlerProbe_1 = __importDefault(require("./withTestButlerProbe")); +const withDetox = (config, { skipProguard, subdomains, includeTestButler } = {}) => { return (0, config_plugins_1.withPlugins)(config, [ // 3. withDetoxProjectGradle_1.default, // 3. - withDetoxTestAppGradle_1.default, + (0, withDetoxTestAppGradle_1.default)(includeTestButler), // 4. [ withKotlinGradle_1.default, @@ -23,11 +25,13 @@ const withDetox = (config, { skipProguard, subdomains } = {}) => { "1.6.10", ], // 5. - withDetoxTestClass_1.withDetoxTestClass, + (0, withDetoxTestClass_1.default)(includeTestButler), // 6. [withNetworkSecurityConfig_1.withNetworkSecurityConfigManifest, { subdomains }], // 7. !skipProguard && withProguardGradle_1.default, + includeTestButler && withTestButlerProbe_1.default, + includeTestButler && withJUnitRunnerClass_1.default, ].filter(Boolean)); }; let pkg = { diff --git a/packages/detox/build/withDetoxTestAppGradle.d.ts b/packages/detox/build/withDetoxTestAppGradle.d.ts index f7e728ea..8b872c4c 100644 --- a/packages/detox/build/withDetoxTestAppGradle.d.ts +++ b/packages/detox/build/withDetoxTestAppGradle.d.ts @@ -6,7 +6,8 @@ import { ConfigPlugin } from "@expo/config-plugins"; * 2. Add `testInstrumentationRunner` to the app/build.gradle * @param config */ -declare const withDetoxTestAppGradle: ConfigPlugin; +declare const withDetoxTestAppGradle: (includeTestButler?: boolean) => ConfigPlugin; export declare function setGradleAndroidTestImplementation(buildGradle: string): string; -export declare function addDetoxDefaultConfigBlock(buildGradle: string): string; +export declare function setGradleAndroidTestImplementationForTestButler(buildGradle: string): string; +export declare function addDetoxDefaultConfigBlock(buildGradle: string, testRunnerClass: string): string; export default withDetoxTestAppGradle; diff --git a/packages/detox/build/withDetoxTestAppGradle.js b/packages/detox/build/withDetoxTestAppGradle.js index a65649e2..95e3944f 100644 --- a/packages/detox/build/withDetoxTestAppGradle.js +++ b/packages/detox/build/withDetoxTestAppGradle.js @@ -1,7 +1,11 @@ "use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.addDetoxDefaultConfigBlock = exports.setGradleAndroidTestImplementation = void 0; +exports.addDetoxDefaultConfigBlock = exports.setGradleAndroidTestImplementationForTestButler = exports.setGradleAndroidTestImplementation = void 0; const config_plugins_1 = require("@expo/config-plugins"); +const node_assert_1 = __importDefault(require("node:assert")); /** * [Step 3](https://github.com/wix/Detox/blob/master/docs/Introduction.Android.md#3-add-the-native-detox-dependency). Add the Native Detox dependency. * @@ -9,17 +13,29 @@ const config_plugins_1 = require("@expo/config-plugins"); * 2. Add `testInstrumentationRunner` to the app/build.gradle * @param config */ -const withDetoxTestAppGradle = (config) => { - return (0, config_plugins_1.withAppBuildGradle)(config, (config) => { - if (config.modResults.language === "groovy") { - config.modResults.contents = setGradleAndroidTestImplementation(config.modResults.contents); - config.modResults.contents = addDetoxDefaultConfigBlock(config.modResults.contents); - } - else { - throw new Error("Cannot add Detox maven gradle because the project build.gradle is not groovy"); - } - return config; - }); +const withDetoxTestAppGradle = (includeTestButler) => { + return (config) => { + const packageName = config.android?.package; + (0, node_assert_1.default)(packageName, "android.package must be defined"); + const testRunnerClass = includeTestButler + ? `${packageName}.DetoxTestAppJUnitRunner` + : "androidx.test.runner.AndroidJUnitRunner"; + console.log(includeTestButler, testRunnerClass); + return (0, config_plugins_1.withAppBuildGradle)(config, (config) => { + if (config.modResults.language === "groovy") { + config.modResults.contents = setGradleAndroidTestImplementation(config.modResults.contents); + config.modResults.contents = addDetoxDefaultConfigBlock(config.modResults.contents, testRunnerClass); + if (includeTestButler) { + config.modResults.contents = + setGradleAndroidTestImplementationForTestButler(config.modResults.contents); + } + } + else { + throw new Error("Cannot add Detox maven gradle because the project build.gradle is not groovy"); + } + return config; + }); + }; }; function setGradleAndroidTestImplementation(buildGradle) { const pattern = /androidTestImplementation\('com.wix:detox:\+'\)/g; @@ -30,7 +46,16 @@ function setGradleAndroidTestImplementation(buildGradle) { androidTestImplementation('com.wix:detox:+')`); } exports.setGradleAndroidTestImplementation = setGradleAndroidTestImplementation; -function addDetoxDefaultConfigBlock(buildGradle) { +function setGradleAndroidTestImplementationForTestButler(buildGradle) { + const pattern = /androidTestImplementation 'com\.linkedin\.testbutler:test-butler-library:2\.2\.1'/g; + if (buildGradle.match(pattern)) { + return buildGradle; + } + return buildGradle.replace(/dependencies\s?{/, `dependencies { + androidTestImplementation 'com.linkedin.testbutler:test-butler-library:2.2.1'`); +} +exports.setGradleAndroidTestImplementationForTestButler = setGradleAndroidTestImplementationForTestButler; +function addDetoxDefaultConfigBlock(buildGradle, testRunnerClass) { const pattern = /detox-plugin-default-config/g; if (buildGradle.match(pattern)) { return buildGradle; @@ -38,7 +63,7 @@ function addDetoxDefaultConfigBlock(buildGradle) { return buildGradle.replace(/defaultConfig\s?{/, `defaultConfig { // detox-plugin-default-config testBuildType System.getProperty('testBuildType', 'debug') - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'`); + testInstrumentationRunner '${testRunnerClass}'`); } exports.addDetoxDefaultConfigBlock = addDetoxDefaultConfigBlock; exports.default = withDetoxTestAppGradle; diff --git a/packages/detox/build/withDetoxTestClass.d.ts b/packages/detox/build/withDetoxTestClass.d.ts index 4ba546cc..91aade87 100644 --- a/packages/detox/build/withDetoxTestClass.d.ts +++ b/packages/detox/build/withDetoxTestClass.d.ts @@ -2,4 +2,5 @@ import { ConfigPlugin } from "@expo/config-plugins"; /** * [Step 5](https://github.com/wix/Detox/blob/master/docs/Introduction.Android.md#5-create-a-detox-test-class). Create `DetoxTest.java` */ -export declare const withDetoxTestClass: ConfigPlugin; +declare const withDetoxTestClass: (includeTestButler?: boolean) => ConfigPlugin; +export default withDetoxTestClass; diff --git a/packages/detox/build/withDetoxTestClass.js b/packages/detox/build/withDetoxTestClass.js index f4dfec19..1d3a17a9 100644 --- a/packages/detox/build/withDetoxTestClass.js +++ b/packages/detox/build/withDetoxTestClass.js @@ -3,7 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.withDetoxTestClass = void 0; const config_plugins_1 = require("@expo/config-plugins"); const assert_1 = __importDefault(require("assert")); const fs_1 = __importDefault(require("fs")); @@ -13,7 +12,7 @@ const path_1 = __importDefault(require("path")); * * @param androidPackage */ -function getTemplateFile(androidPackage) { +function getTemplateFile(androidPackage, includeTestButler) { // This shouldn't change in standard Expo apps. // Replace 'MainActivity' with the value of android:name entry in // in AndroidManifest.xml @@ -38,7 +37,11 @@ public class DetoxTest { public ActivityTestRule<${mainApplicationClassName}> mActivityRule = new ActivityTestRule<>(${mainApplicationClassName}.class, false, false); @Test - public void runDetoxTests() { + public void runDetoxTests() {${includeTestButler + ? ` + TestButlerProbe.assertReadyIfInstalled(); + ` + : ""} DetoxConfig detoxConfig = new DetoxConfig(); detoxConfig.idlePolicyConfig.masterTimeoutSec = 90; detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60; @@ -52,17 +55,19 @@ public class DetoxTest { /** * [Step 5](https://github.com/wix/Detox/blob/master/docs/Introduction.Android.md#5-create-a-detox-test-class). Create `DetoxTest.java` */ -const withDetoxTestClass = (config) => { - return (0, config_plugins_1.withDangerousMod)(config, [ - "android", - async (config) => { - const packageName = config.android?.package; - (0, assert_1.default)(packageName, "android.package must be defined"); - const folder = path_1.default.join(config.modRequest.platformProjectRoot, `app/src/androidTest/java/${packageName.split(".").join("/")}`); - fs_1.default.mkdirSync(folder, { recursive: true }); - fs_1.default.writeFileSync(path_1.default.join(folder, "DetoxTest.java"), getTemplateFile(packageName), { encoding: "utf8" }); - return config; - }, - ]); +const withDetoxTestClass = (includeTestButler) => { + return (config) => { + return (0, config_plugins_1.withDangerousMod)(config, [ + "android", + async (config) => { + const packageName = config.android?.package; + (0, assert_1.default)(packageName, "android.package must be defined"); + const folder = path_1.default.join(config.modRequest.platformProjectRoot, `app/src/androidTest/java/${packageName.split(".").join("/")}`); + fs_1.default.mkdirSync(folder, { recursive: true }); + fs_1.default.writeFileSync(path_1.default.join(folder, "DetoxTest.java"), getTemplateFile(packageName, includeTestButler), { encoding: "utf8" }); + return config; + }, + ]); + }; }; -exports.withDetoxTestClass = withDetoxTestClass; +exports.default = withDetoxTestClass; diff --git a/packages/detox/src/withDetox.ts b/packages/detox/src/withDetox.ts index 033e4526..f9ecd764 100644 --- a/packages/detox/src/withDetox.ts +++ b/packages/detox/src/withDetox.ts @@ -6,13 +6,15 @@ import { import withDetoxProjectGradle from "./withDetoxProjectGradle"; import withDetoxTestAppGradle from "./withDetoxTestAppGradle"; -import { withDetoxTestClass } from "./withDetoxTestClass"; +import withDetoxTestClass from "./withDetoxTestClass"; +import withJUnitRunnerClass from "./withJUnitRunnerClass"; import withKotlinGradle from "./withKotlinGradle"; import { withNetworkSecurityConfigManifest, SubdomainsType, } from "./withNetworkSecurityConfig"; import withProguardGradle from "./withProguardGradle"; +import withTestButlerProbe from "./withTestButlerProbe"; const withDetox: ConfigPlugin< { @@ -30,15 +32,21 @@ const withDetox: ConfigPlugin< * @default ['10.0.2.2', 'localhost'] // (Google emulators) */ subdomains?: SubdomainsType; + /** + * Enable Test Butler library injection in `app/build.grade` and modifications to JUnit test runner + * + * @default false + */ + includeTestButler?: boolean; } | void -> = (config, { skipProguard, subdomains } = {}) => { +> = (config, { skipProguard, subdomains, includeTestButler } = {}) => { return withPlugins( config, [ // 3. withDetoxProjectGradle, // 3. - withDetoxTestAppGradle, + withDetoxTestAppGradle(includeTestButler), // 4. [ withKotlinGradle, @@ -46,11 +54,13 @@ const withDetox: ConfigPlugin< "1.6.10", ], // 5. - withDetoxTestClass, + withDetoxTestClass(includeTestButler), // 6. [withNetworkSecurityConfigManifest, { subdomains }], // 7. !skipProguard && withProguardGradle, + includeTestButler && withTestButlerProbe, + includeTestButler && withJUnitRunnerClass, ].filter(Boolean) as ([ConfigPlugin, any] | ConfigPlugin)[] ); }; diff --git a/packages/detox/src/withDetoxTestAppGradle.ts b/packages/detox/src/withDetoxTestAppGradle.ts index 86ed67a9..6d3ec37a 100644 --- a/packages/detox/src/withDetoxTestAppGradle.ts +++ b/packages/detox/src/withDetoxTestAppGradle.ts @@ -1,4 +1,5 @@ import { ConfigPlugin, withAppBuildGradle } from "@expo/config-plugins"; +import assert from "node:assert"; /** * [Step 3](https://github.com/wix/Detox/blob/master/docs/Introduction.Android.md#3-add-the-native-detox-dependency). Add the Native Detox dependency. @@ -7,22 +8,42 @@ import { ConfigPlugin, withAppBuildGradle } from "@expo/config-plugins"; * 2. Add `testInstrumentationRunner` to the app/build.gradle * @param config */ -const withDetoxTestAppGradle: ConfigPlugin = (config) => { - return withAppBuildGradle(config, (config) => { - if (config.modResults.language === "groovy") { - config.modResults.contents = setGradleAndroidTestImplementation( - config.modResults.contents - ); - config.modResults.contents = addDetoxDefaultConfigBlock( - config.modResults.contents - ); - } else { - throw new Error( - "Cannot add Detox maven gradle because the project build.gradle is not groovy" - ); - } - return config; - }); +const withDetoxTestAppGradle: (includeTestButler?: boolean) => ConfigPlugin = ( + includeTestButler +) => { + return (config) => { + const packageName = config.android?.package; + assert(packageName, "android.package must be defined"); + + const testRunnerClass = includeTestButler + ? `${packageName}.DetoxTestAppJUnitRunner` + : "androidx.test.runner.AndroidJUnitRunner"; + console.log(includeTestButler, testRunnerClass) + + return withAppBuildGradle(config, (config) => { + if (config.modResults.language === "groovy") { + config.modResults.contents = setGradleAndroidTestImplementation( + config.modResults.contents + ); + config.modResults.contents = addDetoxDefaultConfigBlock( + config.modResults.contents, + testRunnerClass + ); + + if (includeTestButler) { + config.modResults.contents = + setGradleAndroidTestImplementationForTestButler( + config.modResults.contents + ); + } + } else { + throw new Error( + "Cannot add Detox maven gradle because the project build.gradle is not groovy" + ); + } + return config; + }); + }; }; export function setGradleAndroidTestImplementation( @@ -39,7 +60,25 @@ export function setGradleAndroidTestImplementation( ); } -export function addDetoxDefaultConfigBlock(buildGradle: string): string { +export function setGradleAndroidTestImplementationForTestButler( + buildGradle: string +): string { + const pattern = + /androidTestImplementation 'com\.linkedin\.testbutler:test-butler-library:2\.2\.1'/g; + if (buildGradle.match(pattern)) { + return buildGradle; + } + return buildGradle.replace( + /dependencies\s?{/, + `dependencies { + androidTestImplementation 'com.linkedin.testbutler:test-butler-library:2.2.1'` + ); +} + +export function addDetoxDefaultConfigBlock( + buildGradle: string, + testRunnerClass: string +): string { const pattern = /detox-plugin-default-config/g; if (buildGradle.match(pattern)) { return buildGradle; @@ -50,7 +89,7 @@ export function addDetoxDefaultConfigBlock(buildGradle: string): string { `defaultConfig { // detox-plugin-default-config testBuildType System.getProperty('testBuildType', 'debug') - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'` + testInstrumentationRunner '${testRunnerClass}'` ); } diff --git a/packages/detox/src/withDetoxTestClass.ts b/packages/detox/src/withDetoxTestClass.ts index bf2eb552..37faed4a 100644 --- a/packages/detox/src/withDetoxTestClass.ts +++ b/packages/detox/src/withDetoxTestClass.ts @@ -8,7 +8,10 @@ import path from "path"; * * @param androidPackage */ -function getTemplateFile(androidPackage: string): string { +function getTemplateFile( + androidPackage: string, + includeTestButler?: boolean +): string { // This shouldn't change in standard Expo apps. // Replace 'MainActivity' with the value of android:name entry in // in AndroidManifest.xml @@ -33,7 +36,13 @@ public class DetoxTest { public ActivityTestRule<${mainApplicationClassName}> mActivityRule = new ActivityTestRule<>(${mainApplicationClassName}.class, false, false); @Test - public void runDetoxTests() { + public void runDetoxTests() {${ + includeTestButler + ? ` + TestButlerProbe.assertReadyIfInstalled(); + ` + : "" + } DetoxConfig detoxConfig = new DetoxConfig(); detoxConfig.idlePolicyConfig.masterTimeoutSec = 90; detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60; @@ -48,23 +57,29 @@ public class DetoxTest { /** * [Step 5](https://github.com/wix/Detox/blob/master/docs/Introduction.Android.md#5-create-a-detox-test-class). Create `DetoxTest.java` */ -export const withDetoxTestClass: ConfigPlugin = (config) => { - return withDangerousMod(config, [ - "android", - async (config) => { - const packageName = config.android?.package; - assert(packageName, "android.package must be defined"); - const folder = path.join( - config.modRequest.platformProjectRoot, - `app/src/androidTest/java/${packageName.split(".").join("/")}` - ); - fs.mkdirSync(folder, { recursive: true }); - fs.writeFileSync( - path.join(folder, "DetoxTest.java"), - getTemplateFile(packageName), - { encoding: "utf8" } - ); - return config; - }, - ]); +const withDetoxTestClass: ( + includeTestButler?: boolean +) => ConfigPlugin = (includeTestButler) => { + return (config) => { + return withDangerousMod(config, [ + "android", + async (config) => { + const packageName = config.android?.package; + assert(packageName, "android.package must be defined"); + const folder = path.join( + config.modRequest.platformProjectRoot, + `app/src/androidTest/java/${packageName.split(".").join("/")}` + ); + fs.mkdirSync(folder, { recursive: true }); + fs.writeFileSync( + path.join(folder, "DetoxTest.java"), + getTemplateFile(packageName, includeTestButler), + { encoding: "utf8" } + ); + return config; + }, + ]); + }; }; + +export default withDetoxTestClass; diff --git a/packages/detox/src/withJUnitRunnerClass.ts b/packages/detox/src/withJUnitRunnerClass.ts new file mode 100644 index 00000000..3671b46b --- /dev/null +++ b/packages/detox/src/withJUnitRunnerClass.ts @@ -0,0 +1,57 @@ +import { ConfigPlugin, withDangerousMod } from '@expo/config-plugins'; +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; + +/** + * Copied from the [react native demo](https://github.com/wix/Detox/blob/master/examples/demo-react-native/android/app/src/androidTest/java/com/example/DetoxTestAppJUnitRunner.java). + * + * @param androidPackage + */ +function getTemplateFile(androidPackage: string): string { + return `package ${androidPackage}; + +import android.os.Bundle; + +import com.linkedin.android.testbutler.TestButler; + +import androidx.test.runner.AndroidJUnitRunner; + +public class DetoxTestAppJUnitRunner extends AndroidJUnitRunner { + @Override + public void onStart() { + TestButler.setup(getTargetContext()); + super.onStart(); + } + + @Override + public void finish(int resultCode, Bundle results) { + TestButler.teardown(getTargetContext()); + super.finish(resultCode, results); + } +} +`; +} + +const withJUnitRunnerClass: ConfigPlugin = (config) => { + return withDangerousMod(config, [ + 'android', + async (config) => { + const packageName = config.android?.package; + assert(packageName, 'android.package must be defined'); + const folder = path.join( + config.modRequest.platformProjectRoot, + `app/src/androidTest/java/${packageName.split('.').join('/')}`, + ); + fs.mkdirSync(folder, { recursive: true }); + fs.writeFileSync( + path.join(folder, 'DetoxTestAppJUnitRunner.java'), + getTemplateFile(packageName), + { encoding: 'utf8' }, + ); + return config; + }, + ]); +}; + +export default withJUnitRunnerClass; diff --git a/packages/detox/src/withTestButlerProbe.ts b/packages/detox/src/withTestButlerProbe.ts new file mode 100644 index 00000000..4a221132 --- /dev/null +++ b/packages/detox/src/withTestButlerProbe.ts @@ -0,0 +1,85 @@ +import { ConfigPlugin, withDangerousMod } from '@expo/config-plugins'; +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; + +/** + * Copied from the [react native demo](https://github.com/wix/Detox/blob/master/examples/demo-react-native/android/app/src/androidTest/java/com/example/TestButlerProbe.java). + * + * @param androidPackage + */ +function getTemplateFile(androidPackage: string): string { + return `package ${androidPackage}; + +import android.content.pm.PackageManager; +import android.util.Log; +import android.view.Surface; + +import com.linkedin.android.testbutler.TestButler; + +import androidx.test.platform.app.InstrumentationRegistry; + +class TestButlerProbe { + + private static final String LOG_TAG = TestButlerProbe.class.getSimpleName(); + private static final String TEST_BUTLER_PACKAGE_NAME = "com.linkedin.android.testbutler"; + + private TestButlerProbe() { + } + + static void assertReadyIfInstalled() { + Log.i(LOG_TAG, "Test butler service verification started..."); + + if (!isTestButlerServiceInstalled()) { + Log.w(LOG_TAG, "Test butler not installed on device - skipping verification"); + return; + } + + assertTestButlerServiceReady(); + Log.i(LOG_TAG, "Test butler service is up and running!"); + } + + static private boolean isTestButlerServiceInstalled() { + try { + PackageManager pm = InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); + pm.getPackageInfo(TEST_BUTLER_PACKAGE_NAME, 0); + return true; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + static private void assertTestButlerServiceReady() { + try { + // This has no effect if test-butler is running. However, if it is not, then unlike TestButler.setup(), it would hard-fail. + TestButler.setRotation(Surface.ROTATION_0); + } catch (Exception e) { + throw new RuntimeException("Test butler service is NOT ready!", e); + } + } +} +`; +} + +const withTestButlerProbe: ConfigPlugin = (config) => { + return withDangerousMod(config, [ + 'android', + async (config) => { + const packageName = config.android?.package; + assert(packageName, 'android.package must be defined'); + const folder = path.join( + config.modRequest.platformProjectRoot, + `app/src/androidTest/java/${packageName.split('.').join('/')}`, + ); + fs.mkdirSync(folder, { recursive: true }); + fs.writeFileSync( + path.join(folder, 'TestButlerProbe.java'), + getTemplateFile(packageName), + { encoding: 'utf8' }, + ); + return config; + }, + ]); +}; + +export default withTestButlerProbe;