chore: update package.json for testing scripts and dependencies
- Added unit and end-to-end testing scripts to package.json. - Included detox for end-to-end testing and jest-circus for improved test execution. feat: add testID to LandingScreen logo for better testing - Added testID attribute to the logo image in LandingScreen for easier identification in tests. feat: create tokens.ts to re-export design tokens - Introduced tokens.ts to re-export shared design tokens from lightTheme for consistent imports.
This commit is contained in:
@@ -84,6 +84,7 @@ android {
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
@@ -110,6 +111,7 @@ android {
|
||||
dependencies {
|
||||
// The version of react-native is set by the React Native Gradle Plugin
|
||||
implementation("com.facebook.react:react-android")
|
||||
androidTestImplementation('com.wix:detox:+')
|
||||
|
||||
if (hermesEnabled.toBoolean()) {
|
||||
implementation("com.facebook.react:hermes-android")
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.mymobileagent;
|
||||
|
||||
import com.wix.detox.Detox;
|
||||
import com.wix.detox.config.DetoxConfig;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class DetoxTest {
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<MainActivity> mActivityRule =
|
||||
new ActivityTestRule<>(MainActivity.class, false, false);
|
||||
|
||||
@Test
|
||||
public void runDetoxTests() {
|
||||
DetoxConfig detoxConfig = new DetoxConfig();
|
||||
detoxConfig.idlePolicyConfig.masterTimeoutSec = 90;
|
||||
detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60;
|
||||
detoxConfig.rnContextLoadTimeoutSec = (BuildConfig.DEBUG ? 180 : 60);
|
||||
|
||||
Detox.runTests(mActivityRule, detoxConfig);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:allowBackup="false"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="${usesCleartextTraffic}"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:supportsRtl="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">10.0.2.2</domain>
|
||||
<domain includeSubdomains="true">localhost</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
@@ -19,3 +19,12 @@ buildscript {
|
||||
}
|
||||
|
||||
apply plugin: "com.facebook.react.rootproject"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven {
|
||||
url("$rootDir/../node_modules/detox/Detox-android")
|
||||
}
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
48
app/detox.config.js
Normal file
48
app/detox.config.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Detox configuration with both attached-device and emulator options.
|
||||
* A helper script (e2e/run-detox.js) will pick the attached device if present,
|
||||
* otherwise fall back to the emulator configuration.
|
||||
*/
|
||||
module.exports = {
|
||||
testRunner: {
|
||||
$0: 'jest',
|
||||
args: {
|
||||
config: 'e2e/jest.config.js',
|
||||
_: ['e2e']
|
||||
}
|
||||
},
|
||||
devices: {
|
||||
'android.attached': {
|
||||
type: 'android.attached',
|
||||
device: {
|
||||
adbName: '.*' // match any attached device
|
||||
}
|
||||
},
|
||||
'android.emulator': {
|
||||
type: 'android.emulator',
|
||||
device: {
|
||||
avdName: 'Pixel_3a_API_30_x86'
|
||||
}
|
||||
}
|
||||
},
|
||||
apps: {
|
||||
'android.debug': {
|
||||
type: 'android.apk',
|
||||
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
|
||||
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..',
|
||||
testBinaryPath: 'android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk',
|
||||
reversePorts: [8081],
|
||||
launchTimeout: 120000 // wait up to 2 min for the app to connect (Metro must be running)
|
||||
}
|
||||
},
|
||||
configurations: {
|
||||
'android.attached+android.debug': {
|
||||
device: 'android.attached',
|
||||
app: 'android.debug'
|
||||
},
|
||||
'android.emu.debug+android.debug': {
|
||||
device: 'android.emulator',
|
||||
app: 'android.debug'
|
||||
}
|
||||
}
|
||||
};
|
||||
19
app/e2e/first.e2e.js
Normal file
19
app/e2e/first.e2e.js
Normal file
@@ -0,0 +1,19 @@
|
||||
describe('App basic e2e', () => {
|
||||
beforeAll(async () => {
|
||||
await device.launchApp({ newInstance: true });
|
||||
});
|
||||
|
||||
it('shows the landing splash screen', async () => {
|
||||
// The landing screen shows a logo for 5 seconds before navigating
|
||||
await waitFor(element(by.id('landing-logo')))
|
||||
.toBeVisible()
|
||||
.withTimeout(10000);
|
||||
});
|
||||
|
||||
it('navigates to main tabs after the splash', async () => {
|
||||
// Landing screen auto-navigates after 5s
|
||||
await waitFor(element(by.text('Modèles')))
|
||||
.toBeVisible()
|
||||
.withTimeout(10000);
|
||||
});
|
||||
});
|
||||
11
app/e2e/jest.config.js
Normal file
11
app/e2e/jest.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
maxWorkers: 1,
|
||||
rootDir: '..',
|
||||
testMatch: ['<rootDir>/e2e/**/*.e2e.[jt]s?(x)'],
|
||||
testTimeout: 120000,
|
||||
verbose: true,
|
||||
reporters: ['detox/runners/jest/reporter'],
|
||||
globalSetup: 'detox/runners/jest/globalSetup',
|
||||
globalTeardown: 'detox/runners/jest/globalTeardown',
|
||||
testEnvironment: 'detox/runners/jest/testEnvironment'
|
||||
};
|
||||
109
app/e2e/run-detox.js
Normal file
109
app/e2e/run-detox.js
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env node
|
||||
const { exec, spawn } = require('child_process');
|
||||
|
||||
const ROOT_DIR = __dirname + '/..';
|
||||
|
||||
function run(cmd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const p = exec(cmd, { cwd: ROOT_DIR, maxBuffer: 1024 * 1024 * 10 });
|
||||
p.stdout.pipe(process.stdout);
|
||||
p.stderr.pipe(process.stderr);
|
||||
p.on('close', code => (code === 0 ? resolve() : reject(new Error(cmd + ' exited with ' + code))));
|
||||
});
|
||||
}
|
||||
|
||||
function adbList() {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec('adb devices -l', (err, stdout) => {
|
||||
if (err) return reject(err);
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Metro is already listening on port 8081.
|
||||
*/
|
||||
function isMetroRunning() {
|
||||
return new Promise(resolve => {
|
||||
exec('lsof -iTCP:8081 -sTCP:LISTEN -t', (err, stdout) => {
|
||||
resolve(!err && stdout.trim().length > 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Metro bundler in the background. Returns a cleanup function.
|
||||
*/
|
||||
function startMetro() {
|
||||
console.log('[run-detox] Starting Metro bundler on port 8081...');
|
||||
const metro = spawn('npx', ['react-native', 'start', '--port', '8081', '--reset-cache'], {
|
||||
cwd: ROOT_DIR,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
});
|
||||
metro.stdout.on('data', d => process.stdout.write('[metro] ' + d));
|
||||
metro.stderr.on('data', d => process.stderr.write('[metro] ' + d));
|
||||
metro.on('error', err => console.error('[run-detox] Metro error:', err.message));
|
||||
return metro;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until Metro is ready (listening on 8081), with a timeout.
|
||||
*/
|
||||
function waitForMetro(timeoutMs = 60000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const start = Date.now();
|
||||
const interval = setInterval(() => {
|
||||
exec('lsof -iTCP:8081 -sTCP:LISTEN -t', (err, stdout) => {
|
||||
if (!err && stdout.trim().length > 0) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
} else if (Date.now() - start > timeoutMs) {
|
||||
clearInterval(interval);
|
||||
reject(new Error('Timed out waiting for Metro to start on port 8081'));
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
(async () => {
|
||||
let metroProcess = null;
|
||||
try {
|
||||
const out = await adbList();
|
||||
const lines = out.split('\n').slice(1).map(l => l.trim()).filter(Boolean);
|
||||
// filter out emulator entries (emulator-*) and look for 'device' state
|
||||
const attached = lines
|
||||
.map(l => l.split(/\s+/)[0])
|
||||
.filter(id => id && !id.startsWith('emulator-'));
|
||||
|
||||
const useConfig = attached.length > 0 ? 'android.attached+android.debug' : 'android.emu.debug+android.debug';
|
||||
console.log('\n[run-detox] Detected attached devices:', attached.join(', ') || '(none)');
|
||||
console.log('[run-detox] Using configuration:', useConfig, '\n');
|
||||
|
||||
// Ensure Metro is running (required for debug APK to load the JS bundle)
|
||||
const metroAlready = await isMetroRunning();
|
||||
if (metroAlready) {
|
||||
console.log('[run-detox] Metro is already running on port 8081.');
|
||||
} else {
|
||||
metroProcess = startMetro();
|
||||
console.log('[run-detox] Waiting for Metro to be ready (up to 60s)...');
|
||||
await waitForMetro(60000);
|
||||
console.log('[run-detox] Metro is ready.\n');
|
||||
}
|
||||
|
||||
console.log('Building...');
|
||||
await run(`npx detox build -c ${useConfig}`);
|
||||
console.log('Running tests...');
|
||||
await run(`npx detox test -c ${useConfig}`);
|
||||
} catch (err) {
|
||||
console.error('\n[run-detox] Error:', err.message || err);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
if (metroProcess) {
|
||||
console.log('\n[run-detox] Stopping Metro bundler...');
|
||||
metroProcess.kill('SIGTERM');
|
||||
}
|
||||
}
|
||||
})();
|
||||
1177
app/package-lock.json
generated
1177
app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,13 @@
|
||||
"ios": "react-native run-ios",
|
||||
"lint": "eslint .",
|
||||
"start": "react-native start",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"test:unit": "jest",
|
||||
"test:e2e:build:android": "detox build -c android.emu.debug",
|
||||
"test:e2e:run:android": "detox test -c android.emu.debug",
|
||||
"test:e2e:android": "npm run test:e2e:build:android && npm run test:e2e:run:android",
|
||||
"test:full": "npm run test:unit && npm run test:e2e:android",
|
||||
"test:e2e:auto": "node ./e2e/run-detox.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
@@ -51,7 +57,9 @@
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "2.8.8",
|
||||
"react-test-renderer": "19.2.3",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.8.3",
|
||||
"detox": "^20.47.0",
|
||||
"jest-circus": "^29.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 22.11.0"
|
||||
|
||||
@@ -35,6 +35,7 @@ export default function LandingScreen() {
|
||||
style={styles.logo}
|
||||
resizeMode="contain"
|
||||
accessibilityLabel="My Mobile Agent"
|
||||
testID="landing-logo"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
3
app/src/theme/tokens.ts
Normal file
3
app/src/theme/tokens.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// Re-export shared design tokens from lightTheme so both
|
||||
// "theme/tokens" and "theme/lightTheme" imports resolve correctly.
|
||||
export { colors, spacing, typography, borderRadius } from './lightTheme';
|
||||
Reference in New Issue
Block a user