Skip to content

Commit 84d007e

Browse files
author
Luca Forstner
authored
fix(e2e): Fix various issues with concurrent E2E and Canary tests (#7805)
1 parent 5d58d52 commit 84d007e

30 files changed

+129
-54
lines changed

.github/workflows/build.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@ jobs:
749749
yarn test:integration:ci
750750
751751
job_e2e_tests:
752-
name: E2E Tests
752+
name: E2E Tests (Shard ${{ matrix.shard }})
753753
# We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks
754754
# Dependabot PRs sadly also don't have access to secrets, so we skip them as well
755755
if:
@@ -758,6 +758,10 @@ jobs:
758758
needs: [job_get_metadata, job_build]
759759
runs-on: ubuntu-20.04
760760
timeout-minutes: 20
761+
strategy:
762+
fail-fast: false
763+
matrix:
764+
shard: [1, 2, 3]
761765
steps:
762766
- name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
763767
uses: actions/checkout@v3
@@ -782,6 +786,8 @@ jobs:
782786
E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }}
783787
E2E_TEST_SENTRY_ORG_SLUG: 'sentry-javascript-sdks'
784788
E2E_TEST_SENTRY_TEST_PROJECT: 'sentry-javascript-e2e-tests'
789+
E2E_TEST_SHARD: ${{ matrix.shard }}
790+
E2E_TEST_SHARD_AMOUNT: 3
785791
run: |
786792
cd packages/e2e-tests
787793
yarn test:e2e

.github/workflows/canary.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
job_canary_test:
2121
name: Canary Tests
2222
runs-on: ubuntu-20.04
23-
timeout-minutes: 30
23+
timeout-minutes: 60
2424
steps:
2525
- name: 'Check out current commit'
2626
uses: actions/checkout@v3

packages/e2e-tests/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ E2E_TEST_AUTH_TOKEN=
22
E2E_TEST_DSN=
33
E2E_TEST_SENTRY_ORG_SLUG=
44
E2E_TEST_SENTRY_TEST_PROJECT=
5+
E2E_TEST_SHARD= # optional
6+
E2E_TEST_SHARD_AMOUNT= # optional
7+
CANARY_E2E_TEST= # optional

packages/e2e-tests/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ To get you started with the recipe, you can copy the following into `test-recipe
5454
{
5555
"$schema": "../../test-recipe-schema.json",
5656
"testApplicationName": "My New Test Application",
57-
"buildCommand": "yarn install --network-concurrency 1",
57+
"buildCommand": "yarn install",
5858
"tests": [
5959
{
6060
"testName": "My new test",

packages/e2e-tests/lib/buildApp.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/* eslint-disable no-console */
22

33
import * as fs from 'fs-extra';
4+
import * as os from 'os';
45
import * as path from 'path';
56

67
import { DEFAULT_BUILD_TIMEOUT_SECONDS } from './constants';
78
import type { Env, RecipeInstance } from './types';
8-
import { spawnAsync } from './utils';
9+
import { prefixObjectKeys, spawnAsync } from './utils';
910

10-
export async function buildApp(appDir: string, recipeInstance: RecipeInstance, env: Env): Promise<void> {
11+
export async function buildApp(appDir: string, recipeInstance: RecipeInstance, envVars: Env): Promise<void> {
1112
const { recipe, label, dependencyOverrides } = recipeInstance;
1213

1314
const packageJsonPath = path.resolve(appDir, 'package.json');
@@ -28,13 +29,23 @@ export async function buildApp(appDir: string, recipeInstance: RecipeInstance, e
2829
if (recipe.buildCommand) {
2930
console.log(`Running build command for test application "${label}"`);
3031

32+
fs.mkdirSync(path.join(os.tmpdir(), 'e2e-test-yarn-caches'), { recursive: true });
33+
const tempYarnCache = fs.mkdtempSync(path.join(os.tmpdir(), 'e2e-test-yarn-caches', 'cache-'));
34+
35+
const env = {
36+
...process.env,
37+
...envVars,
38+
YARN_CACHE_FOLDER: tempYarnCache, // Use a separate yarn cache for each build commmand because multiple yarn commands running at the same time may corrupt the cache
39+
};
40+
3141
const buildResult = await spawnAsync(recipe.buildCommand, {
3242
cwd: appDir,
3343
timeout: (recipe.buildTimeoutSeconds ?? DEFAULT_BUILD_TIMEOUT_SECONDS) * 1000,
3444
env: {
35-
...process.env,
3645
...env,
37-
} as unknown as NodeJS.ProcessEnv,
46+
...prefixObjectKeys(env, 'NEXT_PUBLIC_'),
47+
...prefixObjectKeys(env, 'REACT_APP_'),
48+
},
3849
});
3950

4051
if (buildResult.error) {
@@ -57,9 +68,10 @@ export async function buildApp(appDir: string, recipeInstance: RecipeInstance, e
5768
cwd: appDir,
5869
timeout: (recipe.buildTimeoutSeconds ?? DEFAULT_BUILD_TIMEOUT_SECONDS) * 1000,
5970
env: {
60-
...process.env,
6171
...env,
62-
} as unknown as NodeJS.ProcessEnv,
72+
...prefixObjectKeys(env, 'NEXT_PUBLIC_'),
73+
...prefixObjectKeys(env, 'REACT_APP_'),
74+
},
6375
},
6476
buildResult.stdout,
6577
);

packages/e2e-tests/lib/buildRecipeInstances.ts renamed to packages/e2e-tests/lib/constructRecipeInstances.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ import * as fs from 'fs';
22

33
import type { Recipe, RecipeInput, RecipeInstance } from './types';
44

5-
export function buildRecipeInstances(recipePaths: string[]): RecipeInstance[] {
5+
export function constructRecipeInstances(recipePaths: string[]): RecipeInstance[] {
66
const recipes = buildRecipes(recipePaths);
7-
const recipeInstances: RecipeInstance[] = [];
7+
const recipeInstances: Omit<RecipeInstance, 'portModulo' | 'portGap'>[] = [];
88

9-
const basePort = 3001;
10-
11-
recipes.forEach((recipe, i) => {
9+
recipes.forEach(recipe => {
1210
recipe.versions.forEach(version => {
1311
const dependencyOverrides =
1412
Object.keys(version.dependencyOverrides).length > 0 ? version.dependencyOverrides : undefined;
@@ -20,12 +18,19 @@ export function buildRecipeInstances(recipePaths: string[]): RecipeInstance[] {
2018
label: `${recipe.testApplicationName}${dependencyOverridesInformationString}`,
2119
recipe,
2220
dependencyOverrides,
23-
port: basePort + i,
2421
});
2522
});
2623
});
2724

28-
return recipeInstances;
25+
return recipeInstances
26+
.map((instance, i) => ({ ...instance, portModulo: i, portGap: recipeInstances.length }))
27+
.filter((_, i) => {
28+
if (process.env.E2E_TEST_SHARD && process.env.E2E_TEST_SHARD_AMOUNT) {
29+
return (i + Number(process.env.E2E_TEST_SHARD)) % Number(process.env.E2E_TEST_SHARD_AMOUNT) === 0;
30+
} else {
31+
return true;
32+
}
33+
});
2934
}
3035

3136
function buildRecipes(recipePaths: string[]): Recipe[] {

packages/e2e-tests/lib/runAllTestApps.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
/* eslint-disable no-console */
2-
import { buildRecipeInstances } from './buildRecipeInstances';
2+
import * as fs from 'fs';
3+
import * as os from 'os';
4+
import * as path from 'path';
5+
6+
import { constructRecipeInstances } from './constructRecipeInstances';
37
import { buildAndTestApp } from './runTestApp';
48
import type { RecipeInstance, RecipeTestResult } from './types';
59

610
export async function runAllTestApps(
711
recipePaths: string[],
812
envVarsToInject: Record<string, string | undefined>,
913
): Promise<void> {
10-
const maxParallel = process.env.CI ? 2 : 5;
14+
const maxParallel = process.env.CI ? 1 : 1; // For now we are disabling parallel execution because it was causing problems (runners were too slow and timeouts happened)
1115

12-
const recipeInstances = buildRecipeInstances(recipePaths);
16+
const recipeInstances = constructRecipeInstances(recipePaths);
1317

1418
const results = await shardPromises(
1519
recipeInstances,
@@ -33,6 +37,8 @@ export async function runAllTestApps(
3337

3438
const failed = results.filter(result => result.buildFailed || result.testFailed);
3539

40+
fs.rmSync(path.join(os.tmpdir(), 'e2e-test-yarn-caches'), { force: true, recursive: true });
41+
3642
if (failed.length) {
3743
console.log(`${failed.length} test(s) failed.`);
3844
process.exit(1);

packages/e2e-tests/lib/runTestApp.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export async function buildAndTestApp(
1515
recipeInstance: RecipeInstance,
1616
envVarsToInject: Record<string, string | undefined>,
1717
): Promise<RecipeTestResult> {
18-
const { recipe, port } = recipeInstance;
18+
const { recipe, portModulo, portGap } = recipeInstance;
1919
const recipeDirname = path.dirname(recipe.path);
2020

2121
const targetDir = path.join(TMP_DIR, `${recipe.testApplicationName}-${tmpDirCount++}`);
@@ -24,7 +24,8 @@ export async function buildAndTestApp(
2424

2525
const env: Env = {
2626
...envVarsToInject,
27-
PORT: port.toString(),
27+
PORT_MODULO: portModulo.toString(),
28+
PORT_GAP: portGap.toString(),
2829
};
2930

3031
try {

packages/e2e-tests/lib/testApp.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { DEFAULT_TEST_TIMEOUT_SECONDS } from './constants';
44
import type { Env, RecipeInstance, TestDef, TestResult } from './types';
5-
import { spawnAsync } from './utils';
5+
import { prefixObjectKeys, spawnAsync } from './utils';
66

77
export async function testApp(appDir: string, recipeInstance: RecipeInstance, env: Env): Promise<TestResult[]> {
88
const { recipe } = recipeInstance;
@@ -15,17 +15,28 @@ export async function testApp(appDir: string, recipeInstance: RecipeInstance, en
1515
return results;
1616
}
1717

18-
async function runTest(appDir: string, recipeInstance: RecipeInstance, test: TestDef, env: Env): Promise<TestResult> {
18+
async function runTest(
19+
appDir: string,
20+
recipeInstance: RecipeInstance,
21+
test: TestDef,
22+
envVars: Env,
23+
): Promise<TestResult> {
1924
const { recipe, label } = recipeInstance;
2025
console.log(`Running test command for test application "${label}", test "${test.testName}"`);
2126

27+
const env = {
28+
...process.env,
29+
...envVars,
30+
};
31+
2232
const testResult = await spawnAsync(test.testCommand, {
2333
cwd: appDir,
2434
timeout: (recipe.testTimeoutSeconds ?? DEFAULT_TEST_TIMEOUT_SECONDS) * 1000,
2535
env: {
26-
...process.env,
2736
...env,
28-
} as unknown as NodeJS.ProcessEnv,
37+
...prefixObjectKeys(env, 'NEXT_PUBLIC_'),
38+
...prefixObjectKeys(env, 'REACT_APP_'),
39+
},
2940
});
3041

3142
if (testResult.error) {

packages/e2e-tests/lib/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ export interface RecipeInstance {
3737
label: string;
3838
recipe: Recipe;
3939
dependencyOverrides?: DependencyOverrides;
40-
port: number;
40+
portModulo: number;
41+
portGap: number;
4142
}
4243

4344
export interface RecipeTestResult extends RecipeInstance {

0 commit comments

Comments
 (0)