Skip to content

feat: Add basic JS/Jest basic support (WIP)#86

Draft
cbasitodx wants to merge 3 commits into
mainfrom
sebas_conde/feat/js_jest_basic_capabilities_clean
Draft

feat: Add basic JS/Jest basic support (WIP)#86
cbasitodx wants to merge 3 commits into
mainfrom
sebas_conde/feat/js_jest_basic_capabilities_clean

Conversation

@cbasitodx

@cbasitodx cbasitodx commented Jun 16, 2026

Copy link
Copy Markdown

What does this PR do?
Adds initial JavaScript/Jest support to ddtest.

This includes:

  • New javascript platform detection and sanity checks for Node.js and dd-trace/ci/init
  • Automatic NODE_OPTIONS=-r dd-trace/ci/init injection for dd-trace-js instrumentation
  • Runtime/OS tag collection from Node.js
  • New jest framework integration with default Jest test include/exclude patterns
  • Jest execution through local node_modules/.bin/jest, custom command override, or npx jest
  • Planner support for frameworks that do not support full test discovery: TIA skippables are not fetched/applied, while generic fast test-file discovery and duration-based splitting still run
  • Unit coverage for the new platform, Jest framework, runner mocks, and planner behavior

Motivation
ddtest currently supports Ruby workloads, but Test Optimization also needs JavaScript coverage. This PR starts that support by adding Jest integration, enabling ddtest to plan and run JavaScript test files while automatically initializing dd-trace-js.

How to test the change?
Run the focused package tests:

go test ./internal/platform ./internal/framework ./internal/planner ./internal/runner

Run the full test suite:

go test ./...

@cbasitodx cbasitodx added enhancement New feature or request go Pull requests that update go code labels Jun 16, 2026

@anmarchenko anmarchenko left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a first pass, it has the right direction and there are 2 main improvement areas:

  • I am not convinced that we really need exclude pattern for frameworks - it makes the implementation more complex than needed
  • somehow there are a lot of duplicated code in the framework.go that repeats what we already have and call in discovery.go package


var ErrFullTestDiscoveryUnsupported = errors.New("full test discovery is not supported")

type FullTestDiscoverySupporter interface {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this smells like an unneeded abstraction layer: we can and should add SupportsFullTestDiscovery to Framework interface and implement it for all frameworks

// If it implements the FullTestDiscoverySupporter interface, then
// delegate the choice to SupportFullTestDiscovery()
func SupportsFullTestDiscovery(f Framework) bool {
supporter, ok := f.(FullTestDiscoverySupporter)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should avoid typecasting - if we remove FullTestDiscoverySupporter interface, this logic will not be needed

Comment thread internal/framework/framework.go Outdated
}

// cleanupDiscoveryFile removes the discovery file, ignoring "not exists" errors
func cleanupDiscoveryFile(filePath string) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it belongs to discovery package (and I think it already exists there)

Comment thread internal/framework/framework.go Outdated
}

// executeDiscoveryCommand runs the discovery command and logs timing
func executeDiscoveryCommand(ctx context.Context, executor ext.CommandExecutor, name string, args []string, envMap map[string]string, frameworkName string) ([]byte, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it also exists in discovery package and I don't think it is needed for JS

Comment thread internal/framework/framework.go Outdated
}

// parseDiscoveryFile reads and parses the test discovery JSON file
func parseDiscoveryFile(filePath string) ([]testoptimization.Test, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as before - reuse discovery package

Comment thread internal/framework/framework.go Outdated
return filepath.Join(rootDir, "**", filePattern)
}

func globTestFiles(pattern string) ([]string, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have discovery.DiscoverTestFiles function and we already call it in the planner:

	// Goroutine 3: Test files discovery (fast, must always complete)
	g.Go(func() error {
		startTime := time.Now()
		slog.Info("Discovering test files (fast)...", "framework", framework.Name())
		var res []string
		if resolvedTestFiles.UseExplicitFiles() {
			res = resolvedTestFiles.ExplicitFiles
		} else {
			var discErr error
			res, discErr = discovery.DiscoverTestFiles(resolvedTestFiles.Pattern, settings.GetTestsExcludePattern())
			if discErr != nil {
				fastDiscoveryErr = discErr
				slog.Warn("Fast test discovery failed", "error", discErr)
				return nil // Don't fail the entire process if full discovery succeeded
			}
		}
		discoveredTestFiles = res
		slog.Info("Discovered test files (fast)", "duration", time.Since(startTime), "count", len(discoveredTestFiles))

		return nil
	})

// BaseDiscoveryEnv returns environment variables required for all test discovery processes.
// These env vars ensure the test framework runs in discovery mode without requiring
// actual Datadog credentials or agent connectivity.
func BaseDiscoveryEnv() map[string]string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this already lives in discovery.go:

// BaseEnv returns environment variables required for all test discovery processes.
// These env vars ensure the test framework runs in discovery mode without requiring
// actual Datadog credentials or agent connectivity.
func BaseEnv() map[string]string {
	return map[string]string{
		"DD_CIVISIBILITY_ENABLED":                "1",
		"DD_CIVISIBILITY_AGENTLESS_ENABLED":      "true",
		"DD_API_KEY":                             "dummy_key",
		"DD_TEST_OPTIMIZATION_DISCOVERY_ENABLED": "1",
		"DD_TEST_OPTIMIZATION_DISCOVERY_FILE":    TestsFilePath,
	}
}


var (
jestTestFileExtensions = []string{"js", "jsx", "ts", "tsx", "mjs", "cjs"}
jestExcludedDirs = []string{"node_modules", ".git", "dist", "build", "coverage", ".next"}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we use excluded dirs for jest by default and not using the "**/{__tests__/**/*,*.spec,*.test}.{ts,js,tsx,jsx}" pattern?

return nil
}

func effectiveTestExcludePattern(testFramework framework.Framework) string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we avoid this with more precise pattern for jest tests? I think merging exclude patterns could backfire

Comment thread internal/planner/planner.go
@datadog-official

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants