Skip to content

Commit 308e743

Browse files
authored
test(ci): Adjust detectFlakyTests to account for multiple tests in a file (#11653)
Add a test detection heuristic to the detector to roughly count the number of tests in a file. This should reduce the number of times each test is running in case files with multiple tests have changed. Furthermore, this PR also refactors the run count determination logic a bit to make it easier to understand.
1 parent bd526ab commit 308e743

File tree

1 file changed

+71
-18
lines changed

1 file changed

+71
-18
lines changed

dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,30 @@
11
import * as childProcess from 'child_process';
2+
import * as fs from 'fs';
23
import * as path from 'path';
34
import * as glob from 'glob';
45

6+
/**
7+
* The number of browsers we run the tests in.
8+
*/
9+
const NUM_BROWSERS = 3;
10+
11+
/**
12+
* Assume that each test runs for 3s.
13+
*/
14+
const ASSUMED_TEST_DURATION_SECONDS = 3;
15+
16+
/**
17+
* We keep the runtime of the detector if possible under 30min.
18+
*/
19+
const MAX_TARGET_TEST_RUNTIME_SECONDS = 30 * 60;
20+
21+
/**
22+
* Running one test 50x is what we consider enough to detect flakiness.
23+
* Running one test 5x is the bare minimum
24+
*/
25+
const MAX_PER_TEST_RUN_COUNT = 50;
26+
const MIN_PER_TEST_RUN_COUNT = 5;
27+
528
async function run(): Promise<void> {
629
let testPaths: string[] = [];
730

@@ -20,23 +43,8 @@ ${changedPaths.join('\n')}
2043
}
2144
}
2245

23-
let runCount: number;
24-
if (process.env.TEST_RUN_COUNT === 'AUTO') {
25-
// No test paths detected: run everything 5x
26-
runCount = 5;
27-
28-
if (testPaths.length > 0) {
29-
// Run everything up to 100x, assuming that total runtime is less than 60min.
30-
// We assume an average runtime of 3s per test, times 4 (for different browsers) = 12s per detected testPaths
31-
// We want to keep overall runtime under 30min
32-
const testCount = testPaths.length * 4;
33-
const expectedRuntimePerTestPath = testCount * 3;
34-
const expectedRuntime = Math.floor((30 * 60) / expectedRuntimePerTestPath);
35-
runCount = Math.min(50, Math.max(expectedRuntime, 5));
36-
}
37-
} else {
38-
runCount = parseInt(process.env.TEST_RUN_COUNT || '10');
39-
}
46+
const repeatEachCount = getPerTestRunCount(testPaths);
47+
console.log(`Running tests ${repeatEachCount} times each.`);
4048

4149
const cwd = path.join(__dirname, '../');
4250

@@ -45,7 +53,7 @@ ${changedPaths.join('\n')}
4553
const cp = childProcess.spawn(
4654
`npx playwright test ${
4755
testPaths.length ? testPaths.join(' ') : './suites'
48-
} --reporter='line' --repeat-each ${runCount}`,
56+
} --reporter='line' --repeat-each ${repeatEachCount}`,
4957
{ shell: true, cwd },
5058
);
5159

@@ -88,6 +96,33 @@ ${changedPaths.join('\n')}
8896
console.log(`☑️ All tests passed.`);
8997
}
9098

99+
/**
100+
* Returns how many time one test should run based on the chosen mode and a bunch of heuristics
101+
*/
102+
function getPerTestRunCount(testPaths: string[]) {
103+
if (process.env.TEST_RUN_COUNT === 'AUTO' && testPaths.length > 0) {
104+
// Run everything up to 100x, assuming that total runtime is less than 60min.
105+
// We assume an average runtime of 3s per test, times 4 (for different browsers) = 12s per detected testPaths
106+
// We want to keep overall runtime under 30min
107+
const estimatedNumberOfTests = testPaths.map(getApproximateNumberOfTests).reduce((a, b) => a + b);
108+
console.log(`Estimated number of tests: ${estimatedNumberOfTests}`);
109+
110+
// tests are usually run against all browsers we test with, so let's assume this
111+
const testRunCount = estimatedNumberOfTests * NUM_BROWSERS;
112+
console.log(`Estimated test runs for one round: ${testRunCount}`);
113+
114+
const estimatedTestRuntime = testRunCount * ASSUMED_TEST_DURATION_SECONDS;
115+
console.log(`Estimated test runtime: ${estimatedTestRuntime}s`);
116+
117+
const expectedPerTestRunCount = Math.floor(MAX_TARGET_TEST_RUNTIME_SECONDS / estimatedTestRuntime);
118+
console.log(`Expected per-test run count: ${expectedPerTestRunCount}`);
119+
120+
return Math.min(MAX_PER_TEST_RUN_COUNT, Math.max(expectedPerTestRunCount, MIN_PER_TEST_RUN_COUNT));
121+
}
122+
123+
return parseInt(process.env.TEST_RUN_COUNT || '5');
124+
}
125+
91126
function getTestPaths(): string[] {
92127
const paths = glob.sync('suites/**/test.{ts,js}', {
93128
cwd: path.join(__dirname, '../'),
@@ -111,4 +146,22 @@ function logError(error: unknown) {
111146
}
112147
}
113148

149+
/**
150+
* Definitely not bulletproof way of getting the number of tests in a file :D
151+
* We simply match on `it(`, `test(`, etc and count the matches.
152+
*
153+
* Note: This test completely disregards parameterized tests (`it.each`, etc) or
154+
* skipped/disabled tests and other edge cases. It's just a rough estimate.
155+
*/
156+
function getApproximateNumberOfTests(testPath: string): number {
157+
try {
158+
const content = fs.readFileSync(path.join(process.cwd(), testPath, 'test.ts'), 'utf-8');
159+
const matches = content.match(/it\(|test\(|sentryTest\(/g);
160+
return Math.max(matches ? matches.length : 1, 1);
161+
} catch (e) {
162+
console.error(`Could not read file ${testPath}`);
163+
return 1;
164+
}
165+
}
166+
114167
run();

0 commit comments

Comments
 (0)