Skip to content

Commit e3488cb

Browse files
[test optimization] Fix retry logic in playwright (#6736)
1 parent 62d20bb commit e3488cb

File tree

2 files changed

+76
-21
lines changed

2 files changed

+76
-21
lines changed

integration-tests/playwright/playwright.spec.js

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ versions.forEach((version) => {
315315
})
316316

317317
contextNewVersions('early flake detection', () => {
318-
it('retries new tests', (done) => {
318+
it('retries new tests', async () => {
319319
receiver.setSettings({
320320
early_flake_detection: {
321321
enabled: true,
@@ -334,7 +334,8 @@ versions.forEach((version) => {
334334
// 'highest-level-describe leading and trailing spaces should work with passing tests',
335335
'highest-level-describe leading and trailing spaces should work with skipped tests',
336336
'highest-level-describe leading and trailing spaces should work with fixme',
337-
'highest-level-describe leading and trailing spaces should work with annotated tests'
337+
// it will be considered new
338+
// 'highest-level-describe leading and trailing spaces should work with annotated tests'
338339
],
339340
'skipped-suite-test.js': [
340341
'should work with fixme root'
@@ -355,23 +356,56 @@ versions.forEach((version) => {
355356
assert.propertyVal(testSession.meta, TEST_EARLY_FLAKE_ENABLED, 'true')
356357

357358
const tests = events.filter(event => event.type === 'test').map(event => event.content)
358-
const newTests = tests.filter(test =>
359+
const newPassingTests = tests.filter(test =>
359360
test.resource.endsWith('should work with passing tests')
360361
)
361-
newTests.forEach(test => {
362+
newPassingTests.forEach(test => {
363+
assert.propertyVal(test.meta, TEST_IS_NEW, 'true')
364+
})
365+
assert.equal(
366+
newPassingTests.length,
367+
NUM_RETRIES_EFD + 1,
368+
'passing test has not been retried the correct number of times'
369+
)
370+
const newAnnotatedTests = tests.filter(test =>
371+
test.resource.endsWith('should work with annotated tests')
372+
)
373+
newAnnotatedTests.forEach(test => {
362374
assert.propertyVal(test.meta, TEST_IS_NEW, 'true')
363375
})
376+
assert.equal(
377+
newAnnotatedTests.length,
378+
NUM_RETRIES_EFD + 1,
379+
'annotated test has not been retried the correct number of times'
380+
)
364381

365-
const retriedTests = tests.filter(test => test.meta[TEST_IS_RETRY] === 'true')
382+
// The only new tests are the passing and annotated tests
383+
const totalNewTests = tests.filter(test => test.meta[TEST_IS_NEW] === 'true')
384+
assert.equal(
385+
totalNewTests.length,
386+
newPassingTests.length + newAnnotatedTests.length,
387+
'total new tests is not the sum of the passing and annotated tests'
388+
)
366389

367-
assert.equal(retriedTests.length, NUM_RETRIES_EFD)
390+
// The only retried tests are the passing and annotated tests
391+
const totalRetriedTests = tests.filter(test => test.meta[TEST_IS_RETRY] === 'true')
392+
assert.equal(
393+
totalRetriedTests.length,
394+
newPassingTests.length - 1 + newAnnotatedTests.length - 1,
395+
'total retried tests is not the sum of the passing and annotated tests'
396+
)
397+
assert.equal(
398+
totalRetriedTests.length,
399+
NUM_RETRIES_EFD * 2,
400+
'total retried tests is not the correct number of times'
401+
)
368402

369-
retriedTests.forEach(test => {
403+
totalRetriedTests.forEach(test => {
370404
assert.propertyVal(test.meta, TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
371405
})
372406

373407
// all but one has been retried
374-
assert.equal(retriedTests.length, newTests.length - 1)
408+
assert.equal(totalRetriedTests.length, totalNewTests.length - 2)
375409
})
376410

377411
childProcess = exec(
@@ -386,9 +420,10 @@ versions.forEach((version) => {
386420
}
387421
)
388422

389-
childProcess.on('exit', () => {
390-
receiverPromise.then(() => done()).catch(done)
391-
})
423+
await Promise.all([
424+
once(childProcess, 'exit'),
425+
receiverPromise
426+
])
392427
})
393428

394429
it('is disabled if DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED is false', (done) => {

packages/datadog-instrumentations/src/playwright.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -846,21 +846,41 @@ addHook({
846846
if (isKnownTestsEnabled) {
847847
const newTests = allTests.filter(isNewTest)
848848

849-
for (const newTest of newTests) {
850-
// No need to filter out attempt to fix tests here because attempt to fix tests are never new
849+
/**
850+
* We could repeat the logic of `applyRepeatEachIndex` here, but it'd be more risky
851+
* as playwright could change it at any time.
852+
*
853+
* `applyRepeatEachIndex` goes through all the tests in a suite and applies the "repeat" logic
854+
* for a single repeat index.
855+
*
856+
* This means that the clone logic is cumbersome:
857+
* - we grab the unique file suites that have new tests
858+
* - we store its project suite
859+
* - we clone each of these file suites for each repeat index
860+
* - we execute `applyRepeatEachIndex` for each of these cloned file suites
861+
* - we add the cloned file suites to the project suite
862+
*/
863+
864+
const fileSuitesWithNewTestsToProjects = new Map()
865+
newTests.forEach(newTest => {
851866
newTest._ddIsNew = true
852867
if (isEarlyFlakeDetectionEnabled && newTest.expectedStatus !== 'skipped' && !newTest._ddIsModified) {
853868
const fileSuite = getSuiteType(newTest, 'file')
854-
const projectSuite = getSuiteType(newTest, 'project')
855-
for (let repeatEachIndex = 1; repeatEachIndex <= earlyFlakeDetectionNumRetries; repeatEachIndex++) {
856-
const copyFileSuite = deepCloneSuite(fileSuite, isNewTest, [
857-
'_ddIsNew',
858-
'_ddIsEfdRetry'
859-
])
860-
applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
861-
projectSuite._addSuite(copyFileSuite)
869+
if (!fileSuitesWithNewTestsToProjects.has(fileSuite)) {
870+
fileSuitesWithNewTestsToProjects.set(fileSuite, getSuiteType(newTest, 'project'))
862871
}
863872
}
873+
})
874+
875+
for (const [fileSuite, projectSuite] of fileSuitesWithNewTestsToProjects.entries()) {
876+
for (let repeatEachIndex = 1; repeatEachIndex <= earlyFlakeDetectionNumRetries; repeatEachIndex++) {
877+
const copyFileSuite = deepCloneSuite(fileSuite, isNewTest, [
878+
'_ddIsNew',
879+
'_ddIsEfdRetry'
880+
])
881+
applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
882+
projectSuite._addSuite(copyFileSuite)
883+
}
864884
}
865885
}
866886

0 commit comments

Comments
 (0)