Skip to content

fix(runner): tag options should override inherited suite options#10203

Closed
OfekDanny wants to merge 6 commits intovitest-dev:mainfrom
OfekDanny:fix/tag-options-override-suite-options
Closed

fix(runner): tag options should override inherited suite options#10203
OfekDanny wants to merge 6 commits intovitest-dev:mainfrom
OfekDanny:fix/tag-options-override-suite-options

Conversation

@OfekDanny
Copy link
Copy Markdown

Summary

Closes #10199

When a test is inside a describe with options (e.g. timeout, repeats, concurrent) and has a tag that defines those same options, the tag options were silently ignored. The old code used:

options = Object.assign({}, suiteOptions, options)

This gave suiteOptions higher priority than the tag options that had already been merged into options, so a tag could never override a suite setting.

Expected priority (lowest → highest):

suite options < tag options < explicit test options

Changes

packages/runner/src/suite.ts

  1. The internal task() function now accepts an optional inheritedSuiteOptions argument and spreads it first in the merge chain — before tag options, before test-own options — so the three levels are correctly ordered.

  2. The outer test factory no longer pre-merges suiteOptions into options before calling task(). Instead it passes suiteOptions as the new third argument. The concurrent/sequential resolution is updated to only inject the resolved value into testOwnOptions when the chainable or the test itself explicitly sets it (so a tag's concurrent value can still override a purely-inherited suite value).

Test plan

  • Added integration test in test/cli/test/test-tags.test.ts covering:
    • Tag timeout/repeats wins over suite timeout/repeats
    • Explicit test timeout still beats the tag
    • Tags apply normally when there is no parent suite
  • All existing tag tests continue to pass

When a test inherits options from its parent suite (e.g. `timeout`,
`repeats`, `concurrent`) and also carries a tag that specifies those
same options, the tag should win — but the previous code used
`Object.assign({}, suiteOptions, options)` which gave suiteOptions the
highest priority, silently ignoring tag overrides.

The correct priority order is:
  suite options < tag options < explicit test options

Changes:
- Pass `suiteOptions` as a third argument to the internal `task()`
  function so it is merged first, before tag options, before test-own
  options.
- Rework the `concurrent`/`sequential` resolution in the inner `test`
  factory so that the suite value is only injected into `testOwnOptions`
  when the chainable or the test itself sets it explicitly, leaving the
  slot empty otherwise so that tag options can fill it.

Fixes vitest-dev#10199

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 26, 2026

Deploy Preview for vitest-dev ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit ff24a5a
🔍 Latest deploy log https://app.netlify.com/projects/vitest-dev/deploys/69f00822278e3200082c01f7
😎 Deploy Preview https://deploy-preview-10203--vitest-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/runner/src/suite.ts Outdated
Comment on lines 455 to 473
const effectiveConcurrent = testOwnOptions.concurrent ?? suiteObj?.concurrent
const concurrent = this.concurrent ?? (!this.sequential && effectiveConcurrent)
const effectiveSequential = testOwnOptions.sequential ?? suiteObj?.sequential
const sequential = this.sequential ?? (!this.concurrent && effectiveSequential)

const hasExplicitConcurrency
= this.concurrent != null
|| this.sequential != null
|| testOwnOptions.concurrent != null
|| testOwnOptions.sequential != null

if (hasExplicitConcurrency) {
if (concurrent != null) {
testOwnOptions = { ...testOwnOptions, concurrent }
}
if (sequential != null) {
testOwnOptions = { ...testOwnOptions, sequential }
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We just removed sequential #10198 so this monster can likely go away. Can you merge main and update this logic?

…ide-suite-options

# Conflicts:
#	packages/runner/src/suite.ts
@OfekDanny
Copy link
Copy Markdown
Author

Good call — merged main and simplified. The block and all the sequential references are gone now. The logic in createSuiteCollector's test function is now just:

const concurrent = this.concurrent ?? testOwnOptions?.concurrent
if (concurrent != null) {
  testOwnOptions = { ...testOwnOptions, concurrent }
}

The original fix still holds: when neither the chainable nor the test's own options set concurrent, we leave testOwnOptions without a concurrent key, so the tag's value propagates correctly through task()'s merge order.

@hi-ogawa
Copy link
Copy Markdown
Collaborator

Please fix CI. There's a new tests added by #10198

@OfekDanny
Copy link
Copy Markdown
Author

Done — merged main and simplified the logic. Since sequential is gone after #10198, the hasExplicitConcurrency block is no longer needed. The current code just does:

const concurrent = this.concurrent ?? testOwnOptions?.concurrent
if (concurrent != null) {
  testOwnOptions = { ...testOwnOptions, concurrent }
}

This keeps the correct priority (suite < tag < test-own/chainable) without any of the old complexity. Also updated the snapshot that #10198 added.

@OfekDanny OfekDanny requested a review from hi-ogawa April 27, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test.tags options doesn't overwrite inherited suite options

3 participants