Skip to content

Commit

Permalink
Merge pull request #37587 from JKobrynski/migrateGroup6FilesToTypeScript
Browse files Browse the repository at this point in the history
[No QA] [TS Migration] Migrate testRunner.js, UnreadIndicatorsTest.js, getIsUsingFakeTimers.js, setupAfterEnv.js, config.dev.js tests to TypeScript
  • Loading branch information
arosiclair authored Mar 12, 2024
2 parents 0dc2151 + 64827f8 commit d67625c
Show file tree
Hide file tree
Showing 16 changed files with 163 additions and 142 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/e2ePerformanceTests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ jobs:
run: npm run e2e-test-runner-build

- name: Copy e2e code into zip folder
run: cp tests/e2e/dist/index.js zip/testRunner.js
run: cp tests/e2e/dist/index.js zip/testRunner.ts

- name: Zip everything in the zip directory up
run: zip -qr App.zip ./zip
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module.exports = {
},
testEnvironment: 'jsdom',
setupFiles: ['<rootDir>/jest/setup.ts', './node_modules/@react-native-google-signin/google-signin/jest/build/setup.js'],
setupFilesAfterEnv: ['<rootDir>/jest/setupAfterEnv.ts', '<rootDir>/tests/perf-test/setupAfterEnv.js'],
setupFilesAfterEnv: ['<rootDir>/jest/setupAfterEnv.ts', '<rootDir>/tests/perf-test/setupAfterEnv.ts'],
cacheDirectory: '<rootDir>/.jest-cache',
moduleNameMapper: {
'\\.(lottie)$': '<rootDir>/__mocks__/fileMock.ts',
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@
"symbolicate:ios": "npx metro-symbolicate main.jsbundle.map",
"symbolicate-release:ios": "scripts/release-profile.js --platform=ios",
"symbolicate-release:android": "scripts/release-profile.js --platform=android",
"test:e2e": "ts-node tests/e2e/testRunner.js --config ./config.local.ts",
"test:e2e:dev": "ts-node tests/e2e/testRunner.js --config ./config.dev.js",
"test:e2e": "ts-node tests/e2e/testRunner.ts --config ./config.local.ts",
"test:e2e:dev": "ts-node tests/e2e/testRunner.ts --config ./config.dev.ts",
"gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh",
"workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh",
"workflow-test:generate": "ts-node workflow_tests/utils/preGenerateTest.js",
"setup-https": "mkcert -install && mkcert -cert-file config/webpack/certificate.pem -key-file config/webpack/key.pem dev.new.expensify.com localhost 127.0.0.1",
"e2e-test-runner-build": "ncc build tests/e2e/testRunner.js -o tests/e2e/dist/"
"e2e-test-runner-build": "ncc build tests/e2e/testRunner.ts -o tests/e2e/dist/"
},
"dependencies": {
"@dotlottie/react-player": "^1.6.3",
Expand Down
19 changes: 1 addition & 18 deletions src/libs/E2E/client.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,6 @@
import Config from '../../../tests/e2e/config';
import Routes from '../../../tests/e2e/server/routes';
import type {NetworkCacheMap, TestConfig} from './types';

type TestResult = {
/** Name of the test */
name: string;

/** The branch where test were running */
branch?: string;

/** Duration in milliseconds */
duration?: number;

/** Optional, if set indicates that the test run failed and has no valid results. */
error?: string;

/** Render count */
renderCount?: number;
};
import type {NetworkCacheMap, TestConfig, TestResult} from './types';

type NativeCommandPayload = {
text: string;
Expand Down
19 changes: 18 additions & 1 deletion src/libs/E2E/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,21 @@ type TestConfig = {
[key: string]: string | {autoFocus: boolean};
};

export type {SigninParams, IsE2ETestSession, NetworkCacheMap, NetworkCacheEntry, TestConfig};
type TestResult = {
/** Name of the test */
name: string;

/** The branch where test were running */
branch?: string;

/** Duration in milliseconds */
duration?: number;

/** Optional, if set indicates that the test run failed and has no valid results. */
error?: string;

/** Render count */
renderCount?: number;
};

export type {SigninParams, IsE2ETestSession, NetworkCacheMap, NetworkCacheEntry, TestConfig, TestResult};
8 changes: 8 additions & 0 deletions src/types/modules/react-native.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,14 @@ declare module 'react-native' {
}
interface PressableProps extends WebPressableProps {}

interface AppStateStatic {
emitCurrentTestState: (status: string) => void;
}

interface LinkingStatic {
setInitialURL: (url: string) => void;
}

/**
* Styles
*/
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ components:
- Orchestrates the test suite.
- Runs the app with the tests on a device
- Responsible for gathering and comparing results
- Located in `e2e/testRunner.js`.
- Located in `e2e/testRunner.ts`.

- Test server:
- A nodeJS application that starts an HTTP server.
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/TestSpec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ phases:
test:
commands:
- cd zip
- node testRunner.js -- --mainAppPath app-e2eRelease.apk --deltaAppPath app-e2edeltaRelease.apk
- node testRunner.ts -- --mainAppPath app-e2eRelease.apk --deltaAppPath app-e2edeltaRelease.apk

artifacts:
- $WORKING_DIRECTORY
6 changes: 5 additions & 1 deletion tests/e2e/config.dev.js → tests/e2e/config.dev.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import type {Config} from './config.local';

const packageName = 'com.expensify.chat.dev';
const appPath = './android/app/build/outputs/apk/development/debug/app-development-debug.apk';

export default {
const config: Config = {
MAIN_APP_PACKAGE: packageName,
DELTA_APP_PACKAGE: packageName,
MAIN_APP_PATH: appPath,
DELTA_APP_PATH: appPath,
RUNS: 8,
BOOT_COOL_DOWN: 5 * 1000,
};

export default config;
1 change: 1 addition & 0 deletions tests/e2e/config.local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ const config: Config = {
};

export default config;
export type {Config};
4 changes: 2 additions & 2 deletions tests/e2e/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {createServer} from 'http';
import type {IncomingMessage, ServerResponse} from 'http';
import {createServer} from 'http';
import type {NativeCommand, TestResult} from '@libs/E2E/client';
import type {NetworkCacheMap, TestConfig} from '@libs/E2E/types';
import config from '../config';
Expand Down Expand Up @@ -166,7 +166,7 @@ const createServerInstance = (): ServerInstance => {
return;
}

const cachedData = networkCache[appInstanceId] || {};
const cachedData = networkCache[appInstanceId] ?? {};
res.end(JSON.stringify(cachedData));
});

Expand Down
57 changes: 30 additions & 27 deletions tests/e2e/testRunner.js → tests/e2e/testRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/* eslint-disable @lwc/lwc/no-async-await,no-restricted-syntax,no-await-in-loop */
import {execSync} from 'child_process';
import fs from 'fs';
import _ from 'underscore';
import type {TestConfig} from '@libs/E2E/types';
import compare from './compare/compare';
import defaultConfig from './config';
import createServerInstance from './server';
Expand All @@ -28,9 +28,11 @@ import * as Logger from './utils/logger';
import sleep from './utils/sleep';
import withFailTimeout from './utils/withFailTimeout';

type Result = Record<string, number[]>;

// VARIABLE CONFIGURATION
const args = process.argv.slice(2);
const getArg = (argName) => {
const getArg = (argName: string): string | undefined => {
const argIndex = args.indexOf(argName);
if (argIndex === -1) {
return undefined;
Expand All @@ -39,13 +41,13 @@ const getArg = (argName) => {
};

let config = defaultConfig;
const setConfigPath = (configPathParam) => {
const setConfigPath = (configPathParam: string | undefined) => {
let configPath = configPathParam;
if (!configPath.startsWith('.')) {
if (!configPath?.startsWith('.')) {
configPath = `./${configPath}`;
}
const customConfig = require(configPath).default;
config = _.extend(defaultConfig, customConfig);
config = Object.assign(defaultConfig, customConfig);
};

if (args.includes('--config')) {
Expand All @@ -54,8 +56,8 @@ if (args.includes('--config')) {
}

// Important: set app path only after correct config file has been loaded
const mainAppPath = getArg('--mainAppPath') || config.MAIN_APP_PATH;
const deltaAppPath = getArg('--deltaAppPath') || config.DELTA_APP_PATH;
const mainAppPath = getArg('--mainAppPath') ?? config.MAIN_APP_PATH;
const deltaAppPath = getArg('--deltaAppPath') ?? config.DELTA_APP_PATH;
// Check if files exists:
if (!fs.existsSync(mainAppPath)) {
throw new Error(`Main app path does not exist: ${mainAppPath}`);
Expand All @@ -76,8 +78,7 @@ try {
}

// START OF TEST CODE

const runTests = async () => {
const runTests = async (): Promise<void> => {
Logger.info('Installing apps and reversing port');
await installApp(config.MAIN_APP_PACKAGE, mainAppPath);
await installApp(config.DELTA_APP_PACKAGE, deltaAppPath);
Expand All @@ -88,36 +89,38 @@ const runTests = async () => {
await server.start();

// Create a dict in which we will store the run durations for all tests
const results = {};
const results: Record<string, Result> = {};

// Collect results while tests are being executed
server.addTestResultListener((testResult) => {
if (testResult.error != null) {
if (testResult?.error != null) {
throw new Error(`Test '${testResult.name}' failed with error: ${testResult.error}`);
}
let result = 0;

if ('duration' in testResult) {
if (testResult?.duration !== undefined) {
if (testResult.duration < 0) {
return;
}
result = testResult.duration;
}
if ('renderCount' in testResult) {
if (testResult?.renderCount !== undefined) {
result = testResult.renderCount;
}

Logger.log(`[LISTENER] Test '${testResult.name}' on '${testResult.branch}' measured ${result}`);
Logger.log(`[LISTENER] Test '${testResult?.name}' on '${testResult?.branch}' measured ${result}`);

if (!results[testResult.branch]) {
if (testResult?.branch && !results[testResult.branch]) {
results[testResult.branch] = {};
}

results[testResult.branch][testResult.name] = (results[testResult.branch][testResult.name] || []).concat(result);
if (testResult?.branch && testResult?.name) {
results[testResult.branch][testResult.name] = (results[testResult.branch][testResult.name] ?? []).concat(result);
}
});

// Function to run a single test iteration
async function runTestIteration(appPackage, iterationText, launchArgs) {
async function runTestIteration(appPackage: string, iterationText: string, launchArgs: Record<string, boolean> = {}): Promise<void> {
Logger.info(iterationText);

// Making sure the app is really killed (e.g. if a prior test run crashed)
Expand All @@ -128,10 +131,9 @@ const runTests = async () => {
await launchApp('android', appPackage, config.ACTIVITY_PATH, launchArgs);

await withFailTimeout(
new Promise((resolve) => {
const cleanup = server.addTestDoneListener(() => {
new Promise<void>((resolve) => {
server.addTestDoneListener(() => {
Logger.success(iterationText);
cleanup();
resolve();
});
}),
Expand All @@ -143,9 +145,9 @@ const runTests = async () => {
}

// Run the tests
const tests = _.values(config.TESTS_CONFIG);
const tests = Object.keys(config.TESTS_CONFIG);
for (let testIndex = 0; testIndex < tests.length; testIndex++) {
const test = _.values(config.TESTS_CONFIG)[testIndex];
const test = Object.values(config.TESTS_CONFIG)[testIndex];

// check if we want to skip the test
if (args.includes('--includes')) {
Expand All @@ -164,7 +166,7 @@ const runTests = async () => {
Logger.info(`Cooling down for ${config.BOOT_COOL_DOWN / 1000}s`);
await sleep(config.BOOT_COOL_DOWN);

server.setTestConfig(test);
server.setTestConfig(test as TestConfig);

const warmupText = `Warmup for test '${test.name}' [${testIndex + 1}/${tests.length}]`;

Expand All @@ -182,7 +184,7 @@ const runTests = async () => {

// We run each test multiple time to average out the results
for (let testIteration = 0; testIteration < config.RUNS; testIteration++) {
const onError = (e) => {
const onError = (e: Error) => {
errorCountRef.errorCount += 1;
if (testIteration === 0 || errorCountRef.errorCount === errorCountRef.allowedExceptions) {
Logger.error("There was an error running the test and we've reached the maximum number of allowed exceptions. Stopping the test run.");
Expand All @@ -191,6 +193,7 @@ const runTests = async () => {
// maximum number of allowed exceptions, we should stop the test run.
throw e;
}
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
Logger.warn(`There was an error running the test. Continuing the test run. Error: ${e}`);
};

Expand All @@ -208,7 +211,7 @@ const runTests = async () => {
// Run the test on the delta app:
await runTestIteration(config.DELTA_APP_PACKAGE, deltaIterationText, launchArgs);
} catch (e) {
onError(e);
onError(e as Error);
}
}
}
Expand All @@ -228,7 +231,7 @@ const run = async () => {

process.exit(0);
} catch (e) {
Logger.info('\n\nE2E test suite failed due to error:', e, '\nPrinting full logs:\n\n');
Logger.info('\n\nE2E test suite failed due to error:', e as string, '\nPrinting full logs:\n\n');

// Write logcat, meminfo, emulator info to file as well:
execSync(`adb logcat -d > ${config.OUTPUT_DIR}/logcat.txt`);
Expand All @@ -237,7 +240,7 @@ const run = async () => {

execSync(`cat ${config.LOG_FILE}`);
try {
execSync(`cat ~/.android/avd/${process.env.AVD_NAME || 'test'}.avd/config.ini > ${config.OUTPUT_DIR}/emulator-config.ini`);
execSync(`cat ~/.android/avd/${process.env.AVD_NAME ?? 'test'}.avd/config.ini > ${config.OUTPUT_DIR}/emulator-config.ini`);
} catch (ignoredError) {
// the error is ignored, as the file might not exist if the test
// run wasn't started with an emulator
Expand Down
File renamed without changes.
Loading

0 comments on commit d67625c

Please sign in to comment.