You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracking list of open API design decisions to settle before the 1.0 release. Items here would be breaking (or potentially breaking) to change after 1.0; deferred / will-add-later / unimplemented items go into separate roadmap issues.
Each item is numbered ([N]) so it can be referenced in discussion (e.g. "let's talk about [5]").
Convention: when you check a box, fill the 3-field template under it so the rationale is captured inline.
[22] Reporter data shape: plain object or wrapper classes (TestModule / TestSuite / TestCase), or mark the whole Reporter interface as Best-effort tier and defer
Picked:
Why:
Refs:
[23] TestResultStatus literal lock-in: commit current values 'pass' | 'fail' | 'skip' | 'todo' as the Stable contract for custom reporters, or keep room to rename
Picked:
Why:
Refs:
[26] browser.browser field shape: single-browser field or browser.instances[] array
Picked:
Why:
Refs:
✅ Decided (32)
[13] shard config object form { count, index } tier: keep as a Stable config-side form alongside the CLI flag, or limit to CLI flag only
Picked: Option 2 — CLI-only. Drop the shard config field; expose sharding only via the --shard <index>/<count> flag.
Why: shard is per-invocation — every CI runner must pass a different index, which a value committed to the shared config file cannot express. Mirrors jest, which carries shard on Argv/GlobalConfig and never adds it to user config types. Internally the resolved shard now rides on an internal ResolvedRstestConfig / NormalizedConfig that the CLI injects; the obsolete per-project shard validation is removed; docs move out of config/test/ into the CLI guide (rstest run -> Run a shard).
[7] pool shape and whether to remove minWorkers before 1.0.0
Picked: Option 1 — keep flat pool; remove the public minWorkers field and the --pool.minWorkers CLI flag. Shape settles on { type?, maxWorkers?, execArgv? }.
Why: minWorkers only kept idle workers alive for isolate: false — niche. Jest exposes only maxWorkers/--runInBand; Vitest v4 removed minWorkers. The internal idle-runner floor still defaults to min(maxWorkers, recommended), so default warm-worker behavior is unchanged — only the user-facing override is dropped. The now-unreachable maxWorkers < minWorkers validation is removed.
[16] chaiConfig tier classification, and whether to keep the field name bound to a specific assertion-library implementation
Picked: Option 1 — keep the top-level chaiConfig?: ChaiConfig field (do not nest under expect), with the type narrowed to a curated whitelist. Tier: Stable.
Why: it is a single self-contained escape hatch for the underlying assertion engine, so a top-level field keeps it discoverable alongside the other test-behavior knobs; nesting under expect would only group without decoupling from chai (the name and type stay chai-derived either way), trading a breaking restructure for no real abstraction gain. The implementation-named key is acceptable because the type is no longer a wide passthrough — it is a curated Pick, so it does not leak chai's full config surface.
Note: Option 1's text above is stale. The type is already narrowed from the old wide Partial<Omit<typeof config, 'useProxy' | 'proxyExcludedKeys' | 'deepEqual'>> to a whitelist — ChaiConfig = Partial<Pick<typeof config, 'showDiff' | 'truncateThreshold'>>. So "narrow the type" is already satisfied by the current code; no further change is required.
Refs: packages/core/src/types/config.ts:13-15,530
[4] rs.getConfig() inclusion: keep alongside setConfig / resetConfig, or limit the runtime-config trio to setConfig / resetConfig
Why: it is the only runtime-readable source of the effective config value, letting a config-agnostic helper adjust relative to the current one (e.g. double testTimeout); public since 0.6.6, so removing it would be breaking. Best-effort keeps room to evolve the returned shape.
Picked: Best-effort. Keep both on the public . entry as-is; defer any /internal move or rename.
Why: their only consumer is core's loadCoverageProvider, which resolves the package by name and dynamic-imports the named exports { pluginCoverage, CoverageProvider } — an internal core↔provider protocol, not an end-user extension API. The shapes still move (collect() carries an uncleaned outputModule param; pluginCoverage depends on Rsbuild internals like CHAIN_ID / SWC loader options), so Best-effort keeps 1.x adjustment room. Promote to Stable only if a real third-party provider extension point lands.
[29] createRstest / initCli / runCLI exposure: @internal JSDoc / move to /internal subpath / promote to documented Stable
Picked: split — promote a curated programmatic surface to a dedicated @rstest/core/api subpath (documented Stable), and remove all programmatic/orchestration symbols from the main . entry (which now exposes only in-test runtime globals, config helpers, and types). Public on /api: async createRstest() factory → RstestInstance (run / listTests / mergeReports / addReporter / close) and a jest-compatible runCli({ argv?, cwd? }). Internal-only (not exported): initCli, the old sync host factory (renamed createRstest → createRstestContext), and the old runCLI.
Why: the previous main entry leaked host/CLI orchestration symbols next to in-test globals, mixing two audiences; a dedicated /api subpath gives the programmatic runner a stable, predictable shape modeled on rsbuild's createRsbuild. Per-build config/projects are re-resolved inside the async factory, and per-invocation selection (filters / related / changed / shard / bail) moves to RunOptions, so the construction-vs-run split is explicit.
[9] extends field semantics: adapter function (ExtendConfigFn) / config-file path inheritance / both
Picked: keep current semantics — extends?: ExtendConfigFn | ExtendConfig | (ExtendConfigFn | ExtendConfig)[]; do NOT add a vitest-style string config-file path branch in 1.0. Tier: Stable.
Why: the function form is rstest's adapter bridge — withRslibConfig() / withRsbuildConfig() return ExtendConfigFn, so narrowing to a string path would break every adapter user. Crucially the scope models don't align: vitest's extends lives only on project-inline entries (TestProjectInlineConfiguration, default-isolated, opt-in inheritance) and has no root-level form, so copying it would force the common single-project + adapter user to wrap themselves in a projects array just to extend. rstest's extends is root-level adapter injection AND already works inside projects inline entries (each runs resolveExtends). The union is open, so adding a string branch later stays non-breaking; string-base sharing is already covered by importing an ExtendConfig object or by projects layering, so it's not a 1.0 must-have.
Why: they are the require() mirror of the ESM mock set (mock / doMock / unmock / doUnmock / importMock / importActual) — mockRequire is the direct migration target for jest.mock() on require()-based code, and requireActual / requireMock mirror jest.requireActual / jest.requireMock. Splitting a mirror pair across tiers is a DX wart, and the user contract ("require() returns the mock / actual module") stays stable regardless of how the underlying Rspack→native-ESM runtime evolves.
Why: adds type so handlers can filter by stream, a non-breaking positional extension of the existing single-arg form. More arguments (e.g. the source test entity) may be appended later, also non-breaking, so they stay deferred.
Why: the documented type signature is already () => Rstest, which is a public contract; the chainable shape is consistent across ~25 namespace methods. Downgrading to void would break declared return-type expectations.
[5] testEnvironment field name: testEnvironment or environment
Picked: keep testEnvironment
Why: coupled with [18] — the bare environment name would collide with rsbuild's own environments config concept, so the test-prefixed field stays self-explanatory and keeps the config field and --testEnvironment CLI flag mirrored.
Refs: packages/core/src/types/config.ts:346
[6] testEnvironment field shape: nested { name, options } object, or split into *Environment + *EnvironmentOptions two fields
Picked: Keep the current nested object form — testEnvironment?: EnvironmentName | { name: EnvironmentName; options? }, normalized internally to { name, options }. Do not split into separate testEnvironment + testEnvironmentOptions top-level fields. The string shorthand (testEnvironment: 'jsdom') stays the primary form. Tier: Stable.
Why: name and options belong to one environment unit, so the nested shape states "configure one environment" directly and keeps a single field to evolve (e.g. a future discriminated { name, options } per environment). Avoids a breaking rename/restructure, and testEnvironment.options has near-zero real usage so the flat-field ergonomics gain is marginal.
[11] coverage.reporters field name: plural reporters (current, mirrors the CLI flag and the top-level reporters config field) or singular reporter
Picked: plural reporters
Why: it takes an array and stays symmetric with the top-level reporters field and the --coverage.reporters CLI flag, so the whole reporter surface shares one plural convention.
Refs: packages/core/src/types/coverage.ts:105-118
[12] exclude object form { patterns, override } tier: keep as a Stable extension alongside the string[] form, or limit to string[] only
Picked: Keep the object form as a Stable extension alongside string[].
Why: override: true is only expressible via the object form — the sole opt-out for replacing the built-in defaults (node_modules, dist) instead of extending them. (Contrast jest's testPathIgnorePatterns, a plain array that replaces defaults, forcing users to re-list /node_modules/.)
[14] hideSkippedTestFiles retention: keep as a separate field alongside hideSkippedTests, or fold into hideSkippedTests semantics
Picked: keep both fields
Why: orthogonal granularities — hideSkippedTestFiles drops an entirely-skipped file's whole title block; hideSkippedTests only hides individual skipped case lines in a still-printed file. Folding loses the ability to hide stray skipped cases while still surfacing that a whole file was skipped.
Why: it is a bundler-native core extension controlling test-time externalization with settled semantics; the boolean | BundleDependencyPattern[] shape already allows additive extension, so locking it Stable costs little.
[18] --testEnvironment CLI flag name (coupled with [5])
Picked: keep --testEnvironment
Why: rsbuild already owns an environments config concept, so the test-prefixed name avoids colliding with it and keeps the flag self-explanatory; it also mirrors the testEnvironment config field.
[19] --config-loader tier classification, since the loader strategy enum (auto / jiti / native) may evolve
Picked: keep, full passthrough; Best-effort
Why: --config-loader forwards LoadConfigOptions['loader'] straight to @rsbuild/core's config loader; the auto/jiti/native set is owned by @rsbuild/core, so its evolution is outside rstest's semver control — nothing to implement, just pass through.
[20] --hideSkippedTestFiles and --trace (Perfetto tracing) tier classification, since both relate to surfaces whose semantics or format may still evolve
Picked: Both Stable.
Why: Their only foreseeable evolution is adding optional flag arguments, which is non-breaking — committing them as Stable now costs nothing.
Refs:
[24] md built-in reporter name tier classification: Stable / Best-effort
Picked: keep md, Stable
Why: the name is a stable, self-descriptive built-in; MdReporterOptions fields are all optional so it stays additive, and locking it avoids breaking configs that reference 'md'.
[25] browser.provider literal union: lock to 'playwright' only or open up
Picked: lock to 'playwright' only
Why: only playwright is implemented today; widening the union with additional literals later is a non-breaking change, while starting open ((string & {})) and tightening later would be breaking. Runtime already enforces this via SUPPORTED_PROVIDERS = ['playwright'].
[27] DevicePreset enum literal lock-in for browser.viewport: commit the current 17 preset ids as Stable, or keep room to rename
Picked: Lock the 17 DevicePreset ids as Stable, and widen BrowserViewport to a half-open form: { width: number; height: number } | DevicePreset | (string & {}).
Why: the 17 ids mirror Chrome DevTools device names — stable source, rarely renamed — so the long-term cost of a Stable guarantee is low, and users already write these literals into config/CI so 1.0 must not break them. The (string & {}) widening keeps autocomplete for the known ids while letting the runtime accept custom strings; adding new presets later stays a non-breaking addition (removing an id would require a major).
Picked: Internal — removed the src/index.ts re-export so they have zero published exposure.
Why: They were never public (no @public JSDoc, absent from the public . entry). The src/index.ts re-export (→ ./internal) had zero consumers: package internals import them by relative path, and the only cross-package user (@rstest/browser-ui, which is never published) resolves them through a tsconfig-paths source alias, not a package export. Dropping the re-export makes them fully Internal.
[30] Consolidation of ./browser and ./browser-runtime subpaths: merge / rename ./browser-runtime to /internal/browser-runtime / keep both
Picked: Rename ./browser → ./internal/browser and ./browser-runtime → ./internal/browser-runtime; keep the two paths separate (do not merge).
Why: Both subpaths are consumed only by first-party packages (@rstest/browser, @rstest/browser-ui), undocumented, and JSDoc-marked "not part of the public API" — the internal/ prefix makes the path name match that reality. They stay separate because their dependency profiles are opposite: ./internal/browser carries Node-only deps (@rsbuild/core, coverage, logger, getTestEntries), while ./internal/browser-runtime is browser-safe with zero Node deps; merging would drag Node deps into the browser bundle.
[31] Whether globals: true should inject both rs and rstest as global names
Picked: inject both; prefer rs
Why: rs is the preferred name (docs and examples lead with it), but rstest stays injected as a supported alias — dropping it would break existing tests, and keeping both costs nothing.
[33] defineInlineProject vs defineProject consolidation: keep both helpers, or merge inline semantics into defineProject
Picked: keep both helpers
Why: the two are orthogonal — defineInlineProject types entries inside the projects: [...] array, defineProject types a standalone project config file's export; different input types and use sites, so merging would lose per-site autocompletion.
[34] @rstest/browser's validateBrowserConfig exposure: demote to Internal / move to ./internal subpath
Picked: Keep on the ./internal subpath (Internal tier).
Why: It is a config-precondition checker that core invokes internally via loadBrowserModule; test authors never call it. It is already exported only from src/index.ts → the ./internal subpath, never from the Stable . entry (src/browser.ts, which only re-exports ./client/api). Realigned core's ambient type module from @rstest/browser to @rstest/browser/internal to match the subpath the runtime actually loads.
Picked: Best-effort, for toRstestConfig in @rstest/adapter-rsbuild and @rstest/adapter-rspack. @rstest/adapter-rslib has no such export and stays out of scope.
Why: already exported and documented, so not Internal; mapping is bound to upstream rsbuild/rspack config, so Best-effort over Stable. adapter-rslib's omission is structural (conversion inlined in withRslibConfig, not pure), and adding it later is non-breaking.
Why: ./pure exposes the same render / renderHook / cleanup / configure / act API as the default . entry, only without the auto-cleanup beforeEach side effect — the established Testing Library /pure convention. It adds no surface beyond . to stabilize, so it shares the same tier.
Tracking list of open API design decisions to settle before the 1.0 release. Items here would be breaking (or potentially breaking) to change after 1.0; deferred / will-add-later / unimplemented items go into separate roadmap issues.
Each item is numbered ([N]) so it can be referenced in discussion (e.g. "let's talk about [5]").
Convention: when you check a box, fill the 3-field template under it so the rationale is captured inline.
🟠 Options drafted — pending decision (0)
⚪ Not yet reviewed (4)
[21] File-level hook naming:
onTestFileStart/onTestFileReady/onTestFileResultoronTestModuleStart/onTestModuleEnd[22] Reporter data shape: plain object or wrapper classes (
TestModule/TestSuite/TestCase), or mark the wholeReporterinterface as Best-effort tier and defer[23]
TestResultStatusliteral lock-in: commit current values'pass' | 'fail' | 'skip' | 'todo'as the Stable contract for custom reporters, or keep room to rename[26]
browser.browserfield shape: single-browser field orbrowser.instances[]array✅ Decided (32)
[13]
shardconfig object form{ count, index }tier: keep as a Stable config-side form alongside the CLI flag, or limit to CLI flag onlyshardconfig field; expose sharding only via the--shard <index>/<count>flag.Argv/GlobalConfigand never adds it to user config types. Internally the resolved shard now rides on an internalResolvedRstestConfig/NormalizedConfigthat the CLI injects; the obsolete per-project shard validation is removed; docs move out ofconfig/test/into the CLI guide (rstest run-> Run a shard).[7]
poolshape and whether to removeminWorkersbefore 1.0.0pool; remove the publicminWorkersfield and the--pool.minWorkersCLI flag. Shape settles on{ type?, maxWorkers?, execArgv? }.minWorkersonly kept idle workers alive forisolate: false— niche. Jest exposes onlymaxWorkers/--runInBand; Vitest v4 removedminWorkers. The internal idle-runner floor still defaults tomin(maxWorkers, recommended), so default warm-worker behavior is unchanged — only the user-facing override is dropped. The now-unreachablemaxWorkers < minWorkersvalidation is removed.[16]
chaiConfigtier classification, and whether to keep the field name bound to a specific assertion-library implementationchaiConfig?: ChaiConfigfield (do not nest underexpect), with the type narrowed to a curated whitelist. Tier: Stable.expectwould only group without decoupling from chai (the name and type stay chai-derived either way), trading a breaking restructure for no real abstraction gain. The implementation-named key is acceptable because the type is no longer a wide passthrough — it is a curatedPick, so it does not leak chai's full config surface.Partial<Omit<typeof config, 'useProxy' | 'proxyExcludedKeys' | 'deepEqual'>>to a whitelist —ChaiConfig = Partial<Pick<typeof config, 'showDiff' | 'truncateThreshold'>>. So "narrow the type" is already satisfied by the current code; no further change is required.packages/core/src/types/config.ts:13-15,530[4]
rs.getConfig()inclusion: keep alongsidesetConfig/resetConfig, or limit the runtime-config trio tosetConfig/resetConfiggetConfig, tiered Best-effort whilesetConfig/resetConfigstay Stable.testTimeout); public since 0.6.6, so removing it would be breaking. Best-effort keeps room to evolve the returned shape.packages/core/src/runtime/api/utilities.ts:338[35]
@rstest/coverage-istanbul'spluginCoverage/CoverageProvidertier classification: Stable / Best-effort.entry as-is; defer any/internalmove or rename.loadCoverageProvider, which resolves the package by name and dynamic-imports the named exports{ pluginCoverage, CoverageProvider }— an internal core↔provider protocol, not an end-user extension API. The shapes still move (collect()carries an uncleanedoutputModuleparam;pluginCoveragedepends on Rsbuild internals like CHAIN_ID / SWC loader options), so Best-effort keeps 1.x adjustment room. Promote to Stable only if a real third-party provider extension point lands.packages/coverage-istanbul/src/index.ts:1-2,packages/core/src/types/coverage.ts:161,packages/core/src/coverage/index.ts:16-46,packages/core/src/coverage/install.ts:9-12[29]
createRstest/initCli/runCLIexposure:@internalJSDoc / move to/internalsubpath / promote to documented Stable@rstest/core/apisubpath (documented Stable), and remove all programmatic/orchestration symbols from the main.entry (which now exposes only in-test runtime globals, config helpers, and types). Public on/api: asynccreateRstest()factory →RstestInstance(run/listTests/mergeReports/addReporter/close) and a jest-compatiblerunCli({ argv?, cwd? }). Internal-only (not exported):initCli, the old sync host factory (renamedcreateRstest→createRstestContext), and the oldrunCLI./apisubpath gives the programmatic runner a stable, predictable shape modeled on rsbuild'screateRsbuild. Per-build config/projects are re-resolved inside the async factory, and per-invocation selection (filters/related/changed/shard/bail) moves toRunOptions, so the construction-vs-run split is explicit.[9]
extendsfield semantics: adapter function (ExtendConfigFn) / config-file path inheritance / bothextends?: ExtendConfigFn | ExtendConfig | (ExtendConfigFn | ExtendConfig)[]; do NOT add a vitest-stylestringconfig-file path branch in 1.0. Tier: Stable.withRslibConfig()/withRsbuildConfig()returnExtendConfigFn, so narrowing to a string path would break every adapter user. Crucially the scope models don't align: vitest'sextendslives only on project-inline entries (TestProjectInlineConfiguration, default-isolated, opt-in inheritance) and has no root-level form, so copying it would force the common single-project + adapter user to wrap themselves in aprojectsarray just to extend. rstest'sextendsis root-level adapter injection AND already works insideprojectsinline entries (each runsresolveExtends). The union is open, so adding astringbranch later stays non-breaking; string-base sharing is already covered by importing anExtendConfigobject or byprojectslayering, so it's not a 1.0 must-have.packages/core/src/types/config.ts:233-238(union),packages/core/src/config.ts:120-148(resolveExtends),packages/core/src/cli/init.ts:438(per-project resolve),packages/adapter-rslib/src/index.ts:99,packages/adapter-rsbuild/src/index.ts; vitestextends?: string | trueis project-inline-only (packages/vitest/src/node/types/config.ts:1326-1333)[3]
rs.mockRequire/requireMock/requireActual/doMockRequire/unmockRequire/doUnmockRequire(CJS-flavored entries) tier classification: Stable / Best-effort / Internalrequire()mirror of the ESM mock set (mock/doMock/unmock/doUnmock/importMock/importActual) —mockRequireis the direct migration target forjest.mock()onrequire()-based code, andrequireActual/requireMockmirrorjest.requireActual/jest.requireMock. Splitting a mirror pair across tiers is a DX wart, and the user contract ("require()returns the mock / actual module") stays stable regardless of how the underlying Rspack→native-ESM runtime evolves.packages/core/src/types/mock.ts:428-498,website/docs/en/guide/migration/jest.mdx:287[1]
onConsoleLogcallback signature:(content: string) => boolean | voidor(log, type, entity) => boolean | void(content: string, type: 'stdout' | 'stderr') => boolean | voidtypeso handlers can filter by stream, a non-breaking positional extension of the existing single-arg form. More arguments (e.g. the source test entity) may be appended later, also non-breaking, so they stay deferred.[2]
rs.clearAllMocks/resetAllMocks/restoreAllMocksand other reset methods: chainablethisreturn orvoid() => RstestUtilitieschainable return() => Rstest, which is a public contract; the chainable shape is consistent across ~25 namespace methods. Downgrading tovoidwould break declared return-type expectations.packages/core/src/types/mock.ts:390,394,398,website/docs/en/api/runtime-api/rstest/mock-functions.mdx:223,230,237[5]
testEnvironmentfield name:testEnvironmentorenvironmenttestEnvironmentenvironmentname would collide with rsbuild's ownenvironmentsconfig concept, so thetest-prefixed field stays self-explanatory and keeps the config field and--testEnvironmentCLI flag mirrored.packages/core/src/types/config.ts:346[6]
testEnvironmentfield shape: nested{ name, options }object, or split into*Environment+*EnvironmentOptionstwo fieldstestEnvironment?: EnvironmentName | { name: EnvironmentName; options? }, normalized internally to{ name, options }. Do not split into separatetestEnvironment+testEnvironmentOptionstop-level fields. The string shorthand (testEnvironment: 'jsdom') stays the primary form. Tier: Stable.{ name, options }per environment). Avoids a breaking rename/restructure, andtestEnvironment.optionshas near-zero real usage so the flat-field ergonomics gain is marginal.packages/core/src/types/config.ts:238-243,346,604,packages/core/src/config.ts:221-223,331-336[8]
resetMocksvsmockResetfield nameresetMocksclearMocks/restoreMocks(verb + plural noun), so the three reset-family options share one phrase structure.packages/core/src/types/config.ts:447[10]
coverage.providerliteral union: lock to'istanbul' | 'v8'only or open up (e.g.'istanbul' | 'v8' | (string & {}))'istanbul' | 'v8'packages/core/src/types/coverage.ts:102,packages/core/src/coverage/install.ts:9-12[11]
coverage.reportersfield name: pluralreporters(current, mirrors the CLI flag and the top-levelreportersconfig field) or singularreporterreportersreportersfield and the--coverage.reportersCLI flag, so the whole reporter surface shares one plural convention.packages/core/src/types/coverage.ts:105-118[12]
excludeobject form{ patterns, override }tier: keep as a Stable extension alongside thestring[]form, or limit tostring[]onlystring[].override: trueis only expressible via the object form — the sole opt-out for replacing the built-in defaults (node_modules,dist) instead of extending them. (Contrast jest'stestPathIgnorePatterns, a plain array that replaces defaults, forcing users to re-list/node_modules/.)packages/core/src/types/config.ts:278-287,packages/core/src/config.ts:161-173[14]
hideSkippedTestFilesretention: keep as a separate field alongsidehideSkippedTests, or fold intohideSkippedTestssemanticshideSkippedTestFilesdrops an entirely-skipped file's whole title block;hideSkippedTestsonly hides individual skipped case lines in a still-printed file. Folding loses the ability to hide stray skipped cases while still surfacing that a whole file was skipped.packages/core/src/types/config.ts:414,420,packages/core/src/reporter/index.ts:100-103,115-127[15]
output.bundleDependenciestier classification: Stable / Best-effortboolean | BundleDependencyPattern[]shape already allows additive extension, so locking it Stable costs little.packages/core/src/types/config.ts:86,packages/core/src/core/plugins/external.ts:268-286[17] Whether to add a
--reportersplural alias for--reporter--reporters;--reporterkept as a silent alias (no deprecation, no planned removal)reportersconfig field name[18]
--testEnvironmentCLI flag name (coupled with [5])--testEnvironmentenvironmentsconfig concept, so thetest-prefixed name avoids colliding with it and keeps the flag self-explanatory; it also mirrors thetestEnvironmentconfig field.packages/core/src/cli/commands.ts:111,256,packages/core/src/types/config.ts:346[19]
--config-loadertier classification, since the loader strategy enum (auto/jiti/native) may evolve--config-loaderforwardsLoadConfigOptions['loader']straight to@rsbuild/core's config loader; theauto/jiti/nativeset is owned by@rsbuild/core, so its evolution is outside rstest's semver control — nothing to implement, just pass through.packages/core/src/config.ts:69-94,packages/core/src/cli/commands.ts:34,181[20]
--hideSkippedTestFilesand--trace(Perfetto tracing) tier classification, since both relate to surfaces whose semantics or format may still evolve[24]
mdbuilt-in reporter name tier classification: Stable / Best-effortmd, StableMdReporterOptionsfields are all optional so it stays additive, and locking it avoids breaking configs that reference'md'.packages/core/src/types/reporter.ts:31,58,179[25]
browser.providerliteral union: lock to'playwright'only or open up'playwright'only(string & {})) and tightening later would be breaking. Runtime already enforces this viaSUPPORTED_PROVIDERS = ['playwright'].packages/core/src/types/config.ts:171,packages/browser/src/configValidation.ts:4[27]
DevicePresetenum literal lock-in forbrowser.viewport: commit the current 17 preset ids as Stable, or keep room to renameDevicePresetids as Stable, and widenBrowserViewportto a half-open form:{ width: number; height: number } | DevicePreset | (string & {}).(string & {})widening keeps autocomplete for the known ids while letting the runtime accept custom strings; adding new presets later stays a non-breaking addition (removing an id would require a major).packages/core/src/types/config.ts:133-157,packages/browser/src/viewportPresets.ts:1-65[28]
BROWSER_VIEWPORT_PRESET_DIMENSIONS/BROWSER_VIEWPORT_PRESET_IDS/resolveBrowserViewportPresetexports (from@rstest/browser) tier classification: Stable / Best-effortsrc/index.tsre-export so they have zero published exposure.@publicJSDoc, absent from the public.entry). Thesrc/index.tsre-export (→./internal) had zero consumers: package internals import them by relative path, and the only cross-package user (@rstest/browser-ui, which is never published) resolves them through a tsconfig-paths source alias, not a package export. Dropping the re-export makes them fully Internal.packages/browser/src/viewportPresets.ts,packages/browser/src/index.ts(re-export removed),packages/browser-ui/tsconfig.json(@rstest/browser/viewport-presetspaths alias), refactor(browser): drop unused internal browser surface #1360[30] Consolidation of
./browserand./browser-runtimesubpaths: merge / rename./browser-runtimeto/internal/browser-runtime/ keep both./browser→./internal/browserand./browser-runtime→./internal/browser-runtime; keep the two paths separate (do not merge).@rstest/browser,@rstest/browser-ui), undocumented, and JSDoc-marked "not part of the public API" — theinternal/prefix makes the path name match that reality. They stay separate because their dependency profiles are opposite:./internal/browsercarries Node-only deps (@rsbuild/core, coverage, logger,getTestEntries), while./internal/browser-runtimeis browser-safe with zero Node deps; merging would drag Node deps into the browser bundle.packages/core/package.json(exports["./internal/browser"],["./internal/browser-runtime"]),packages/core/src/browser.ts:1-5(JSDoc),packages/core/src/browserRuntime.ts:1-9, refactor(core): move internal browser host subpaths under ./internal #1358[31] Whether
globals: trueshould inject bothrsandrstestas global namesrsrsis the preferred name (docs and examples lead with it), butrsteststays injected as a supported alias — dropping it would break existing tests, and keeping both costs nothing.packages/core/src/utils/constants.ts:232-246,packages/core/src/runtime/worker/runInPool.ts:48-56[33]
defineInlineProjectvsdefineProjectconsolidation: keep both helpers, or merge inline semantics intodefineProjectdefineInlineProjecttypes entries inside theprojects: [...]array,defineProjecttypes a standalone project config file's export; different input types and use sites, so merging would lose per-site autocompletion.packages/core/src/index.ts:67-94,packages/core/src/types/config.ts:225-226[34]
@rstest/browser'svalidateBrowserConfigexposure: demote to Internal / move to./internalsubpath./internalsubpath (Internal tier).loadBrowserModule; test authors never call it. It is already exported only fromsrc/index.ts→ the./internalsubpath, never from the Stable.entry (src/browser.ts, which only re-exports./client/api). Realigned core's ambient type module from@rstest/browserto@rstest/browser/internalto match the subpath the runtime actually loads.packages/browser/src/index.ts:12,packages/browser/package.json(exports["./internal"]),packages/core/src/core/browserLoader.ts:81,packages/core/src/env.d.ts:11, refactor(core): move internal browser host subpaths under ./internal #1358[36]
@rstest/adapter-rsbuild'stoRstestConfigexposure: Best-effort / InternaltoRstestConfigin@rstest/adapter-rsbuildand@rstest/adapter-rspack.@rstest/adapter-rslibhas no such export and stays out of scope.withRslibConfig, not pure), and adding it later is non-breaking.packages/adapter-rsbuild/src/index.ts:60,packages/adapter-rsbuild/src/toRstestConfig.ts:79,packages/adapter-rspack/src/index.ts:327,packages/adapter-rslib/src/index.ts:99-153[37]
@rstest/browser-react's./puresubpath tier classification: Stable / Best-effort./pureexposes the samerender/renderHook/cleanup/configure/actAPI as the default.entry, only without the auto-cleanupbeforeEachside effect — the established Testing Library/pureconvention. It adds no surface beyond.to stabilize, so it shares the same tier.packages/browser-react/src/index.ts,packages/browser-react/src/pure.tsx,packages/browser-react/package.json(exports["./pure"])