diff --git a/integration-tests/ci-visibility-intake.js b/integration-tests/ci-visibility-intake.js index 44e6f2d3119..c12ffc274f0 100644 --- a/integration-tests/ci-visibility-intake.js +++ b/integration-tests/ci-visibility-intake.js @@ -161,12 +161,13 @@ class FakeCiVisIntake extends FakeAgent { }) } - async stop () { - await super.stop() + stop () { settings = DEFAULT_SETTINGS suitesToSkip = DEFAULT_SUITES_TO_SKIP gitUploadStatus = DEFAULT_GIT_UPLOAD_STATUS infoResponse = DEFAULT_INFO_RESPONSE + this.removeAllListeners() + return super.stop() } // Similar to gatherPayloads but resolves if enough payloads have been gathered @@ -230,7 +231,7 @@ class FakeCiVisIntake extends FakeAgent { }, timeout || 15000) const messageHandler = (message) => { if (!payloadMatch || payloadMatch(message)) { - clearInterval(timeoutId) + clearTimeout(timeoutId) resolve(message) this.off('message', messageHandler) } diff --git a/integration-tests/ci-visibility.spec.js b/integration-tests/ci-visibility.spec.js index 41176982953..0c07858ad3a 100644 --- a/integration-tests/ci-visibility.spec.js +++ b/integration-tests/ci-visibility.spec.js @@ -455,7 +455,7 @@ testFrameworks.forEach(({ receiver.assertPayloadReceived(() => { const error = new Error('it should not report code coverage') done(error) - }, ({ url }) => url === '/api/v2/citestcov') + }, ({ url }) => url === '/api/v2/citestcov').catch(() => {}) receiver.assertPayloadReceived(({ headers, payload }) => { assert.propertyVal(headers, 'dd-api-key', '1') @@ -774,7 +774,7 @@ testFrameworks.forEach(({ receiver.assertPayloadReceived(() => { const error = new Error('it should not report code coverage') done(error) - }, ({ url }) => url === '/evp_proxy/v2/api/v2/citestcov') + }, ({ url }) => url === '/evp_proxy/v2/api/v2/citestcov').catch(() => {}) receiver.assertPayloadReceived(({ headers, payload }) => { assert.notProperty(headers, 'dd-api-key') diff --git a/integration-tests/cucumber/cucumber.spec.js b/integration-tests/cucumber/cucumber.spec.js index 925766d4df3..c9a037e4c4f 100644 --- a/integration-tests/cucumber/cucumber.spec.js +++ b/integration-tests/cucumber/cucumber.spec.js @@ -317,7 +317,7 @@ versions.forEach(version => { receiver.assertPayloadReceived(() => { const error = new Error('it should not report code coverage') done(error) - }, ({ url }) => url.endsWith('/api/v2/citestcov')) + }, ({ url }) => url.endsWith('/api/v2/citestcov')).catch(() => {}) receiver.assertPayloadReceived(({ payload }) => { const eventTypes = payload.events.map(event => event.type) diff --git a/integration-tests/cypress-config.json b/integration-tests/cypress-config.json index d32da03c6f9..3bd4dc31817 100644 --- a/integration-tests/cypress-config.json +++ b/integration-tests/cypress-config.json @@ -3,5 +3,6 @@ "screenshotOnRunFailure": false, "pluginsFile": "cypress/plugins-old/index.js", "supportFile": "cypress/support/e2e.js", - "integrationFolder": "cypress/e2e" + "integrationFolder": "cypress/e2e", + "defaultCommandTimeout": 100 } diff --git a/integration-tests/cypress.config.js b/integration-tests/cypress.config.js index cbf3c02eb39..cfd53d5a234 100644 --- a/integration-tests/cypress.config.js +++ b/integration-tests/cypress.config.js @@ -1,4 +1,5 @@ module.exports = { + defaultCommandTimeout: 100, e2e: { setupNodeEvents (on, config) { require('dd-trace/ci/cypress/plugin')(on, config) diff --git a/integration-tests/cypress/cypress.spec.js b/integration-tests/cypress/cypress.spec.js index 9b02d56010a..2670f4be0c1 100644 --- a/integration-tests/cypress/cypress.spec.js +++ b/integration-tests/cypress/cypress.spec.js @@ -18,7 +18,10 @@ const { TEST_COMMAND, TEST_MODULE, TEST_FRAMEWORK_VERSION, - TEST_TOOLCHAIN + TEST_TOOLCHAIN, + TEST_CODE_COVERAGE_ENABLED, + TEST_ITR_SKIPPING_ENABLED, + TEST_ITR_TESTS_SKIPPED } = require('../../packages/dd-trace/src/plugins/util/test') const { NODE_MAJOR } = require('../../version') @@ -31,6 +34,7 @@ versions.forEach((version) => { this.retries(2) this.timeout(60000) let sandbox, cwd, receiver, childProcess, webAppPort + const commandSuffix = version === '6.7.0' ? '--config-file cypress-config.json' : '' before(async () => { sandbox = await createSandbox([`cypress@${version}`], true) cwd = sandbox.folder @@ -53,6 +57,107 @@ versions.forEach((version) => { await receiver.stop() }) + it('can run and report tests', (done) => { + receiver.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), payloads => { + const events = payloads.flatMap(({ payload }) => payload.events) + + const testSessionEvent = events.find(event => event.type === 'test_session_end') + const testModuleEvent = events.find(event => event.type === 'test_module_end') + const testSuiteEvents = events.filter(event => event.type === 'test_suite_end') + const testEvents = events.filter(event => event.type === 'test') + + const { content: testSessionEventContent } = testSessionEvent + const { content: testModuleEventContent } = testModuleEvent + + assert.exists(testSessionEventContent.test_session_id) + assert.exists(testSessionEventContent.meta[TEST_COMMAND]) + assert.exists(testSessionEventContent.meta[TEST_TOOLCHAIN]) + assert.equal(testSessionEventContent.resource.startsWith('test_session.'), true) + assert.equal(testSessionEventContent.meta[TEST_STATUS], 'fail') + + assert.exists(testModuleEventContent.test_session_id) + assert.exists(testModuleEventContent.test_module_id) + assert.exists(testModuleEventContent.meta[TEST_COMMAND]) + assert.exists(testModuleEventContent.meta[TEST_MODULE]) + assert.equal(testModuleEventContent.resource.startsWith('test_module.'), true) + assert.equal(testModuleEventContent.meta[TEST_STATUS], 'fail') + assert.equal( + testModuleEventContent.test_session_id.toString(10), + testSessionEventContent.test_session_id.toString(10) + ) + assert.exists(testModuleEventContent.meta[TEST_FRAMEWORK_VERSION]) + + assert.includeMembers(testSuiteEvents.map(suite => suite.content.resource), [ + 'test_suite.cypress/e2e/other.cy.js', + 'test_suite.cypress/e2e/spec.cy.js' + ]) + + assert.includeMembers(testSuiteEvents.map(suite => suite.content.meta[TEST_STATUS]), [ + 'pass', + 'fail' + ]) + + testSuiteEvents.forEach(({ + content: { + meta, + test_suite_id: testSuiteId, + test_module_id: testModuleId, + test_session_id: testSessionId + } + }) => { + assert.exists(meta[TEST_COMMAND]) + assert.exists(meta[TEST_MODULE]) + assert.exists(testSuiteId) + assert.equal(testModuleId.toString(10), testModuleEventContent.test_module_id.toString(10)) + assert.equal(testSessionId.toString(10), testSessionEventContent.test_session_id.toString(10)) + }) + + assert.includeMembers(testEvents.map(test => test.content.resource), [ + 'cypress/e2e/other.cy.js.context passes', + 'cypress/e2e/spec.cy.js.context passes', + 'cypress/e2e/spec.cy.js.other context fails' + ]) + + assert.includeMembers(testEvents.map(test => test.content.meta[TEST_STATUS]), [ + 'pass', + 'pass', + 'fail' + ]) + + testEvents.forEach(({ + content: { + meta, + test_suite_id: testSuiteId, + test_module_id: testModuleId, + test_session_id: testSessionId + } + }) => { + assert.exists(meta[TEST_COMMAND]) + assert.exists(meta[TEST_MODULE]) + assert.exists(testSuiteId) + assert.equal(testModuleId.toString(10), testModuleEventContent.test_module_id.toString(10)) + assert.equal(testSessionId.toString(10), testSessionEventContent.test_session_id.toString(10)) + }) + }, 25000).then(() => done()).catch(done) + + const { + NODE_OPTIONS, // NODE_OPTIONS dd-trace config does not work with cypress + ...restEnvVars + } = getCiVisEvpProxyConfig(receiver.port) + + childProcess = exec( + `./node_modules/.bin/cypress run --quiet ${commandSuffix}`, + { + cwd, + env: { + ...restEnvVars, + CYPRESS_BASE_URL: `http://localhost:${webAppPort}` + }, + stdio: 'pipe' + } + ) + }) + it('can report code coverage if it is available', (done) => { const commandSuffix = version === '6.7.0' ? '--config-file cypress-config.json' : '' @@ -78,8 +183,7 @@ versions.forEach((version) => { .map(file => file.filename) assert.includeMembers(fileNames, Object.keys(coverageFixture)) - done() - }) + }, 20000).then(() => done()).catch(done) childProcess = exec( `./node_modules/.bin/cypress run --quiet ${commandSuffix}`, @@ -94,116 +198,174 @@ versions.forEach((version) => { ) }) - const reportMethods = ['agentless', 'evp proxy'] - - reportMethods.forEach((reportMethod) => { - context(`reporting via ${reportMethod}`, () => { - it('can run and report tests', (done) => { - const envVars = reportMethod === 'agentless' - ? getCiVisAgentlessConfig(receiver.port) : getCiVisEvpProxyConfig(receiver.port) - const reportUrl = reportMethod === 'agentless' ? '/api/v2/citestcycle' : '/evp_proxy/v2/api/v2/citestcycle' - - receiver.gatherPayloadsMaxTimeout(({ url }) => url === reportUrl, payloads => { - const events = payloads.flatMap(({ payload }) => payload.events) - - const testSessionEvent = events.find(event => event.type === 'test_session_end') - const testModuleEvent = events.find(event => event.type === 'test_module_end') - const testSuiteEvents = events.filter(event => event.type === 'test_suite_end') - const testEvents = events.filter(event => event.type === 'test') - - const { content: testSessionEventContent } = testSessionEvent - const { content: testModuleEventContent } = testModuleEvent - - assert.exists(testSessionEventContent.test_session_id) - assert.exists(testSessionEventContent.meta[TEST_COMMAND]) - assert.exists(testSessionEventContent.meta[TEST_TOOLCHAIN]) - assert.equal(testSessionEventContent.resource.startsWith('test_session.'), true) - assert.equal(testSessionEventContent.meta[TEST_STATUS], 'fail') - - assert.exists(testModuleEventContent.test_session_id) - assert.exists(testModuleEventContent.test_module_id) - assert.exists(testModuleEventContent.meta[TEST_COMMAND]) - assert.exists(testModuleEventContent.meta[TEST_MODULE]) - assert.equal(testModuleEventContent.resource.startsWith('test_module.'), true) - assert.equal(testModuleEventContent.meta[TEST_STATUS], 'fail') - assert.equal( - testModuleEventContent.test_session_id.toString(10), - testSessionEventContent.test_session_id.toString(10) - ) - assert.exists(testModuleEventContent.meta[TEST_FRAMEWORK_VERSION]) - - assert.includeMembers(testSuiteEvents.map(suite => suite.content.resource), [ - 'test_suite.cypress/e2e/other.cy.js', - 'test_suite.cypress/e2e/spec.cy.js' - ]) - - assert.includeMembers(testSuiteEvents.map(suite => suite.content.meta[TEST_STATUS]), [ - 'pass', - 'fail' - ]) - - testSuiteEvents.forEach(({ - content: { - meta, - test_suite_id: testSuiteId, - test_module_id: testModuleId, - test_session_id: testSessionId - } - }) => { - assert.exists(meta[TEST_COMMAND]) - assert.exists(meta[TEST_MODULE]) - assert.exists(testSuiteId) - assert.equal(testModuleId.toString(10), testModuleEventContent.test_module_id.toString(10)) - assert.equal(testSessionId.toString(10), testSessionEventContent.test_session_id.toString(10)) - }) - - assert.includeMembers(testEvents.map(test => test.content.resource), [ - 'cypress/e2e/other.cy.js.context passes', - 'cypress/e2e/spec.cy.js.context passes', - 'cypress/e2e/spec.cy.js.other context fails' - ]) - - assert.includeMembers(testEvents.map(test => test.content.meta[TEST_STATUS]), [ - 'pass', - 'pass', - 'fail' - ]) - - testEvents.forEach(({ - content: { - meta, - test_suite_id: testSuiteId, - test_module_id: testModuleId, - test_session_id: testSessionId - } - }) => { - assert.exists(meta[TEST_COMMAND]) - assert.exists(meta[TEST_MODULE]) - assert.exists(testSuiteId) - assert.equal(testModuleId.toString(10), testModuleEventContent.test_module_id.toString(10)) - assert.equal(testSessionId.toString(10), testSessionEventContent.test_session_id.toString(10)) - }) - }, 25000).then(() => done()).catch(done) - - const { - NODE_OPTIONS, // NODE_OPTIONS dd-trace config does not work with cypress - ...restEnvVars - } = envVars - - const commandSuffix = version === '6.7.0' ? '--config-file cypress-config.json' : '' - - childProcess = exec( - `./node_modules/.bin/cypress run --quiet ${commandSuffix}`, - { - cwd, - env: { - ...restEnvVars, - CYPRESS_BASE_URL: `http://localhost:${webAppPort}` - }, - stdio: 'pipe' - } + context('intelligent test runner', () => { + it('can report git metadata', (done) => { + const searchCommitsRequestPromise = receiver.payloadReceived( + ({ url }) => url.endsWith('/api/v2/git/repository/search_commits') + ) + const packfileRequestPromise = receiver + .payloadReceived(({ url }) => url.endsWith('/api/v2/git/repository/packfile')) + + const { + NODE_OPTIONS, // NODE_OPTIONS dd-trace config does not work with cypress + ...restEnvVars + } = getCiVisAgentlessConfig(receiver.port) + + childProcess = exec( + `./node_modules/.bin/cypress run --quiet ${commandSuffix}`, + { + cwd, + env: { + ...restEnvVars, + CYPRESS_BASE_URL: `http://localhost:${webAppPort}` + }, + stdio: 'pipe' + } + ) + childProcess.on('exit', () => { + Promise.all([ + searchCommitsRequestPromise, + packfileRequestPromise + ]).then(([searchCommitRequest, packfileRequest]) => { + assert.propertyVal(searchCommitRequest.headers, 'dd-api-key', '1') + assert.propertyVal(packfileRequest.headers, 'dd-api-key', '1') + done() + }).catch(done) + }) + }) + it('does not report code coverage if disabled by the API', (done) => { + receiver.setSettings({ + code_coverage: false, + tests_skipping: false + }) + + receiver.assertPayloadReceived(() => { + const error = new Error('it should not report code coverage') + done(error) + }, ({ url }) => url.endsWith('/api/v2/citestcov')).catch(() => {}) + + receiver.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => { + const events = payloads.flatMap(({ payload }) => payload.events) + const eventTypes = events.map(event => event.type) + assert.includeMembers(eventTypes, ['test', 'test_session_end', 'test_module_end', 'test_suite_end']) + }).then(() => done()).catch(done) + + const { + NODE_OPTIONS, + ...restEnvVars + } = getCiVisAgentlessConfig(receiver.port) + + childProcess = exec( + `./node_modules/.bin/cypress run --quiet ${commandSuffix}`, + { + cwd, + env: { + ...restEnvVars, + CYPRESS_BASE_URL: `http://localhost:${webAppPort}` + }, + stdio: 'pipe' + } + ) + }) + it('can skip suites received by the intelligent test runner API and still reports code coverage', (done) => { + receiver.setSuitesToSkip([{ + type: 'test', + attributes: { + name: 'context passes', + suite: 'cypress/e2e/other.cy.js' + } + }]) + + const skippableRequestPromise = receiver + .payloadReceived(({ url }) => url.endsWith('/api/v2/ci/tests/skippable')) + const eventsRequestPromise = receiver.payloadReceived(({ url }) => url.endsWith('/api/v2/citestcycle')) + + Promise.all([ + skippableRequestPromise, + eventsRequestPromise + ]).then(([skippableRequest, eventsRequest]) => { + assert.propertyVal(skippableRequest.headers, 'dd-api-key', '1') + assert.propertyVal(skippableRequest.headers, 'dd-application-key', '1') + + const eventTypes = eventsRequest.payload.events.map(event => event.type) + + const skippedTest = eventsRequest.payload.events.find(event => + event.content.resource === 'cypress/e2e/other.cy.js.context passes' ) + assert.notExists(skippedTest) + assert.includeMembers(eventTypes, ['test', 'test_suite_end', 'test_module_end', 'test_session_end']) + + const testSession = eventsRequest.payload.events.find(event => event.type === 'test_session_end').content + assert.propertyVal(testSession.meta, TEST_ITR_TESTS_SKIPPED, 'true') + assert.propertyVal(testSession.meta, TEST_CODE_COVERAGE_ENABLED, 'true') + assert.propertyVal(testSession.meta, TEST_ITR_SKIPPING_ENABLED, 'true') + const testModule = eventsRequest.payload.events.find(event => event.type === 'test_module_end').content + assert.propertyVal(testModule.meta, TEST_ITR_TESTS_SKIPPED, 'true') + assert.propertyVal(testModule.meta, TEST_CODE_COVERAGE_ENABLED, 'true') + assert.propertyVal(testModule.meta, TEST_ITR_SKIPPING_ENABLED, 'true') + done() + }).catch(done) + + const { + NODE_OPTIONS, + ...restEnvVars + } = getCiVisAgentlessConfig(receiver.port) + + childProcess = exec( + `./node_modules/.bin/cypress run --quiet ${commandSuffix}`, + { + cwd, + env: { + ...restEnvVars, + CYPRESS_BASE_URL: `http://localhost:${webAppPort}` + }, + stdio: 'pipe' + } + ) + }) + it('does not skip tests if test skipping is disabled by the API', (done) => { + receiver.setSettings({ + code_coverage: true, + tests_skipping: false }) + + receiver.setSuitesToSkip([{ + type: 'test', + attributes: { + name: 'context passes', + suite: 'cypress/e2e/other.cy.js' + } + }]) + + receiver.assertPayloadReceived(() => { + const error = new Error('should not request skippable') + done(error) + }, ({ url }) => url.endsWith('/api/v2/ci/tests/skippable')).catch(() => {}) + + receiver.gatherPayloadsMaxTimeout(({ url }) => url.endsWith('/api/v2/citestcycle'), (payloads) => { + const events = payloads.flatMap(({ payload }) => payload.events) + const notSkippedTest = events.find(event => + event.content.resource === 'cypress/e2e/other.cy.js.context passes' + ) + assert.exists(notSkippedTest) + }).then(() => done()).catch(done) + + const { + NODE_OPTIONS, + ...restEnvVars + } = getCiVisAgentlessConfig(receiver.port) + + childProcess = exec( + `./node_modules/.bin/cypress run --quiet ${commandSuffix}`, + { + cwd, + env: { + ...restEnvVars, + CYPRESS_BASE_URL: `http://localhost:${webAppPort}` + }, + stdio: 'pipe' + } + ) }) }) }) diff --git a/packages/datadog-plugin-cypress/src/plugin.js b/packages/datadog-plugin-cypress/src/plugin.js index e4b553f4fa6..2050d8f04ce 100644 --- a/packages/datadog-plugin-cypress/src/plugin.js +++ b/packages/datadog-plugin-cypress/src/plugin.js @@ -19,13 +19,14 @@ const { TEST_SOURCE_START, finishAllTraceSpans, getCoveredFilenamesFromCoverage, - getTestSuitePath + getTestSuitePath, + addIntelligentTestRunnerSpanTags } = require('../../dd-trace/src/plugins/util/test') +const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants') +const log = require('../../dd-trace/src/log') const TEST_FRAMEWORK_NAME = 'cypress' -const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants') - const CYPRESS_STATUS_TO_TEST_STATUS = { passed: 'pass', failed: 'fail', @@ -56,7 +57,7 @@ function getCypressVersion (details) { function getRootDir (details) { if (details && details.config) { - return details.config.repoRoot || details.config.projectRoot || process.cwd() + return details.config.projectRoot || details.config.repoRoot || process.cwd() } return process.cwd() } @@ -88,10 +89,62 @@ function getSuiteStatus (suiteStats) { return 'pass' } +function getItrConfig (tracer, testConfiguration) { + return new Promise(resolve => { + if (!tracer._tracer._exporter || !tracer._tracer._exporter.getItrConfiguration) { + return resolve({ err: new Error('CI Visibility was not initialized correctly') }) + } + + tracer._tracer._exporter.getItrConfiguration(testConfiguration, (err, itrConfig) => { + resolve({ err, itrConfig }) + }) + }) +} + +function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration) { + if (!isSuitesSkippingEnabled) { + return Promise.resolve({ skippableTests: [] }) + } + return new Promise(resolve => { + if (!tracer._tracer._exporter || !tracer._tracer._exporter.getItrConfiguration) { + return resolve({ err: new Error('CI Visibility was not initialized correctly') }) + } + tracer._tracer._exporter.getSkippableSuites(testConfiguration, (err, skippableTests) => { + resolve({ + err, + skippableTests + }) + }) + }) +} + module.exports = (on, config) => { const tracer = require('../../dd-trace') const testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME) + const { + 'git.repository_url': repositoryUrl, + 'git.commit.sha': sha, + 'os.version': osVersion, + 'os.platform': osPlatform, + 'os.architecture': osArchitecture, + 'runtime.name': runtimeName, + 'runtime.version': runtimeVersion, + 'git.branch': branch + } = testEnvironmentMetadata + + const testConfiguration = { + repositoryUrl, + sha, + osVersion, + osPlatform, + osArchitecture, + runtimeName, + runtimeVersion, + branch, + testLevel: 'test' + } + const codeOwnersEntries = getCodeOwnersFileEntries() let activeSpan = null @@ -101,32 +154,53 @@ module.exports = (on, config) => { let command = null let frameworkVersion let rootDir + let isSuitesSkippingEnabled = false + let isCodeCoverageEnabled = false + let testsToSkip = [] on('before:run', (details) => { - const childOf = getTestParentSpan(tracer) + return getItrConfig(tracer, testConfiguration).then(({ err, itrConfig }) => { + if (err) { + log.error(err) + } else { + isSuitesSkippingEnabled = itrConfig.isSuitesSkippingEnabled + isCodeCoverageEnabled = itrConfig.isCodeCoverageEnabled + } - rootDir = getRootDir(details) - command = getCypressCommand(details) - frameworkVersion = getCypressVersion(details) + getSkippableTests(isSuitesSkippingEnabled, tracer, testConfiguration).then(({ err, skippableTests }) => { + if (err) { + log.error(err) + } else { + testsToSkip = skippableTests || [] + } - const testSessionSpanMetadata = getTestSessionCommonTags(command, frameworkVersion, TEST_FRAMEWORK_NAME) - const testModuleSpanMetadata = getTestModuleCommonTags(command, frameworkVersion, TEST_FRAMEWORK_NAME) + const childOf = getTestParentSpan(tracer) + rootDir = getRootDir(details) - testSessionSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_session`, { - childOf, - tags: { - [COMPONENT]: TEST_FRAMEWORK_NAME, - ...testEnvironmentMetadata, - ...testSessionSpanMetadata - } - }) - testModuleSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_module`, { - childOf: testSessionSpan, - tags: { - [COMPONENT]: TEST_FRAMEWORK_NAME, - ...testEnvironmentMetadata, - ...testModuleSpanMetadata - } + command = getCypressCommand(details) + frameworkVersion = getCypressVersion(details) + + const testSessionSpanMetadata = getTestSessionCommonTags(command, frameworkVersion, TEST_FRAMEWORK_NAME) + const testModuleSpanMetadata = getTestModuleCommonTags(command, frameworkVersion, TEST_FRAMEWORK_NAME) + + testSessionSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_session`, { + childOf, + tags: { + [COMPONENT]: TEST_FRAMEWORK_NAME, + ...testEnvironmentMetadata, + ...testSessionSpanMetadata + } + }) + testModuleSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_module`, { + childOf: testSessionSpan, + tags: { + [COMPONENT]: TEST_FRAMEWORK_NAME, + ...testEnvironmentMetadata, + ...testModuleSpanMetadata + } + }) + return details + }) }) }) @@ -136,6 +210,16 @@ module.exports = (on, config) => { testModuleSpan.setTag(TEST_STATUS, testStatus) testSessionSpan.setTag(TEST_STATUS, testStatus) + addIntelligentTestRunnerSpanTags( + testSessionSpan, + testModuleSpan, + { + isSuitesSkipped: !!testsToSkip.length, + isSuitesSkippingEnabled, + isCodeCoverageEnabled + } + ) + testModuleSpan.finish() testSessionSpan.finish() @@ -181,6 +265,12 @@ module.exports = (on, config) => { }, 'dd:beforeEach': (test) => { const { testName, testSuite } = test + // skip test + if (testsToSkip.find(test => { + return testName === test.name && testSuite === test.suite + })) { + return { shouldSkip: true } + } const testSuiteTags = { [TEST_COMMAND]: command, @@ -219,12 +309,12 @@ module.exports = (on, config) => { } }) } - return activeSpan ? activeSpan.context().toTraceId() : null + return activeSpan ? { traceId: activeSpan.context().toTraceId() } : {} }, 'dd:afterEach': ({ test, coverage }) => { const { state, error, isRUMActive, testSourceLine } = test if (activeSpan) { - if (coverage && tracer._tracer._exporter.exportCoverage) { + if (coverage && tracer._tracer._exporter.exportCoverage && isCodeCoverageEnabled) { const coverageFiles = getCoveredFilenamesFromCoverage(coverage) const relativeCoverageFiles = coverageFiles.map(file => getTestSuitePath(file, rootDir)) const { _traceId, _spanId } = testSuiteSpan.context() diff --git a/packages/datadog-plugin-cypress/src/support.js b/packages/datadog-plugin-cypress/src/support.js index f88dae721a4..83361b04c74 100644 --- a/packages/datadog-plugin-cypress/src/support.js +++ b/packages/datadog-plugin-cypress/src/support.js @@ -1,10 +1,13 @@ /* eslint-disable */ -beforeEach(() => { +beforeEach(function () { cy.task('dd:beforeEach', { testName: Cypress.mocha.getRunner().suite.ctx.currentTest.fullTitle(), testSuite: Cypress.mocha.getRootSuite().file - }).then(traceId => { + }).then(({ traceId, shouldSkip }) => { Cypress.env('traceId', traceId) + if (shouldSkip) { + this.skip() + } }) }) diff --git a/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js b/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js index ca40233b64c..e37e6b2f598 100644 --- a/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +++ b/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js @@ -12,7 +12,8 @@ function getSkippableSuites ({ osArchitecture, runtimeName, runtimeVersion, - custom + custom, + testLevel = 'suite' }, done) { const options = { path: '/api/v2/ci/tests/skippable', @@ -52,7 +53,7 @@ function getSkippableSuites ({ data: { type: 'test_params', attributes: { - test_level: 'suite', + test_level: testLevel, configurations: { 'os.platform': osPlatform, 'os.version': osVersion, @@ -77,8 +78,13 @@ function getSkippableSuites ({ try { skippableSuites = JSON.parse(res) .data - .filter(({ type }) => type === 'suite') - .map(({ attributes: { suite } }) => suite) + .filter(({ type }) => type === testLevel) + .map(({ attributes: { suite, name } }) => { + if (testLevel === 'suite') { + return suite + } + return { suite, name } + }) done(null, skippableSuites) } catch (err) { done(err)