Skip to content

Commit d5e899b

Browse files
[test optimization] Fix quarantined tests being skipped rather than ignored in cypress (#7442)
1 parent 61ead84 commit d5e899b

File tree

3 files changed

+72
-10
lines changed

3 files changed

+72
-10
lines changed

integration-tests/cypress/cypress.spec.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2700,15 +2700,20 @@ moduleTypes.forEach(({
27002700

27012701
if (isQuarantining) {
27022702
assert.strictEqual(testSession.meta[TEST_MANAGEMENT_ENABLED], 'true')
2703+
// Session status should be 'pass' because Cypress sees the quarantined test as passed
2704+
assert.strictEqual(testSession.meta[TEST_STATUS], 'pass')
27032705
} else {
27042706
assert.ok(!(TEST_MANAGEMENT_ENABLED in testSession.meta))
2707+
assert.strictEqual(testSession.meta[TEST_STATUS], 'fail')
27052708
}
27062709

27072710
assert.strictEqual(failedTest.resource, 'cypress/e2e/quarantine.js.quarantine is quarantined')
27082711

27092712
if (isQuarantining) {
2710-
// TODO: run instead of skipping, but ignore its result
2711-
assert.strictEqual(failedTest.meta[TEST_STATUS], 'skip')
2713+
// Quarantined tests run normally but their failures are suppressed by Cypress.on('fail')
2714+
// in support.js. The test actually fails (reports 'fail' to Datadog) but Cypress sees
2715+
// it as passed, so the exit code is 0.
2716+
assert.strictEqual(failedTest.meta[TEST_STATUS], 'fail')
27122717
assert.strictEqual(failedTest.meta[TEST_MANAGEMENT_IS_QUARANTINED], 'true')
27132718
} else {
27142719
assert.strictEqual(failedTest.meta[TEST_STATUS], 'fail')

packages/datadog-plugin-cypress/src/cypress-plugin.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -769,8 +769,10 @@ class CypressPlugin {
769769
if (cypressTest.displayError) {
770770
latestError = new Error(cypressTest.displayError)
771771
}
772-
// Update test status
773-
if (cypressTestStatus !== finishedTest.testStatus) {
772+
// Update test status - but NOT for quarantined tests where we intentionally
773+
// report 'fail' to Datadog even though Cypress sees it as 'pass'
774+
const isQuarantinedTest = finishedTest.testSpan?.context()?._tags?.[TEST_MANAGEMENT_IS_QUARANTINED] === 'true'
775+
if (cypressTestStatus !== finishedTest.testStatus && !isQuarantinedTest) {
774776
finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
775777
finishedTest.testSpan.setTag('error', latestError)
776778
}
@@ -844,11 +846,12 @@ class CypressPlugin {
844846
return { shouldSkip: true }
845847
}
846848

847-
// TODO: I haven't found a way to trick cypress into ignoring a test
848-
// The way we'll implement quarantine in cypress is by skipping the test altogether
849-
if (!isAttemptToFix && (isDisabled || isQuarantined)) {
849+
// For disabled tests (not attemptToFix), skip them
850+
if (!isAttemptToFix && isDisabled) {
850851
return { shouldSkip: true }
851852
}
853+
// Quarantined tests (not attemptToFix) run normally but their failures are caught
854+
// by Cypress.on('fail') in support.js and suppressed, so Cypress sees them as passed
852855

853856
if (!this.activeTestSpan) {
854857
this.activeTestSpan = this.getTestSpan({
@@ -880,6 +883,7 @@ class CypressPlugin {
880883
isEfdRetry,
881884
isAttemptToFix,
882885
isModified,
886+
isQuarantined: isQuarantinedFromSupport,
883887
} = test
884888
if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
885889
const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
@@ -952,6 +956,12 @@ class CypressPlugin {
952956
}
953957
}
954958

959+
// Ensure quarantined tests reported from support.js are tagged
960+
// (This catches cases where the test ran but failed, but Cypress saw it as passed)
961+
if (isQuarantinedFromSupport) {
962+
this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
963+
}
964+
955965
const finishedTest = {
956966
testName,
957967
testStatus,

packages/datadog-plugin-cypress/src/support.js

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ let isModifiedTest = false
1212
let isTestIsolationEnabled = false
1313
// Array of test names that have been retried and the reason
1414
const retryReasonsByTestName = new Map()
15+
// Track quarantined test errors - we catch them in Cypress.on('fail') but need to report to Datadog
16+
const quarantinedTestErrors = new Map()
1517

1618
// We need to grab the original window as soon as possible,
1719
// in case the test changes the origin. If the test does change the origin,
@@ -46,6 +48,30 @@ function getTestProperties (testName) {
4648
return { isAttemptToFix, isDisabled, isQuarantined }
4749
}
4850

51+
// Catch test failures for quarantined tests and suppress them
52+
// By not re-throwing the error, Cypress marks the test as passed
53+
// This allows quarantined tests to run but not affect the exit code
54+
Cypress.on('fail', (err, runnable) => {
55+
if (!isTestManagementEnabled) {
56+
throw err
57+
}
58+
59+
const testName = runnable.fullTitle()
60+
const { isQuarantined, isAttemptToFix } = getTestProperties(testName)
61+
62+
// For pure quarantined tests (not attemptToFix), suppress the failure
63+
// This makes the test "pass" from Cypress's perspective while we still track the error
64+
if (isQuarantined && !isAttemptToFix) {
65+
// Store the error so we can report it to Datadog in afterEach
66+
quarantinedTestErrors.set(testName, err)
67+
// Don't re-throw - this prevents Cypress from marking the test as failed
68+
return
69+
}
70+
71+
// For all other tests (including attemptToFix), let the error propagate normally
72+
throw err
73+
})
74+
4975
function getRetriedTests (test, numRetries, tags) {
5076
const retriedTests = []
5177
for (let retryIndex = 0; retryIndex < numRetries; retryIndex++) {
@@ -185,16 +211,31 @@ after(() => {
185211

186212
afterEach(function () {
187213
const currentTest = Cypress.mocha.getRunner().suite.ctx.currentTest
214+
const testName = currentTest.fullTitle()
215+
216+
// Check if this was a quarantined test that we suppressed the failure for
217+
const quarantinedError = quarantinedTestErrors.get(testName)
218+
const isQuarantinedTestThatFailed = !!quarantinedError
219+
220+
// For quarantined tests, convert Error to a serializable format for cy.task
221+
const errorToReport = isQuarantinedTestThatFailed
222+
? { message: quarantinedError.message, stack: quarantinedError.stack }
223+
: currentTest.err
224+
188225
const testInfo = {
189-
testName: currentTest.fullTitle(),
226+
testName,
190227
testSuite: Cypress.mocha.getRootSuite().file,
191228
testSuiteAbsolutePath: Cypress.spec && Cypress.spec.absolute,
192-
state: currentTest.state,
193-
error: currentTest.err,
229+
// For quarantined tests, report the actual state (failed) to Datadog, not what Cypress thinks (passed)
230+
state: isQuarantinedTestThatFailed ? 'failed' : currentTest.state,
231+
// For quarantined tests, include the actual error that was suppressed
232+
error: errorToReport,
194233
isNew: currentTest._ddIsNew,
195234
isEfdRetry: currentTest._ddIsEfdRetry,
196235
isAttemptToFix: currentTest._ddIsAttemptToFix,
197236
isModified: currentTest._ddIsModified,
237+
// Mark quarantined tests that failed so the plugin knows to tag them appropriately
238+
isQuarantined: isQuarantinedTestThatFailed,
198239
}
199240
try {
200241
testInfo.testSourceLine = Cypress.mocha.getRunner().currentRunnable.invocationDetails.line
@@ -209,5 +250,11 @@ afterEach(function () {
209250
} catch {
210251
// ignore error and continue
211252
}
253+
254+
// Clean up the quarantined error tracking
255+
if (isQuarantinedTestThatFailed) {
256+
quarantinedTestErrors.delete(testName)
257+
}
258+
212259
cy.task('dd:afterEach', { test: testInfo, coverage })
213260
})

0 commit comments

Comments
 (0)