mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(site): resolve WS/HTTP race condition on workspace parameters page (#22556)
## Problem
Flaky e2e test: `update workspace, new required, mutable parameter
added`
```
Error: Timed out 15000ms waiting for expect(locator).toHaveValue(expected)
Locator: getByTestId('parameter-field-Sixth parameter').locator('input')
Expected string: "99"
Received string: ""
```
## Root Cause
When the workspace parameters page loads, the WebSocket sends an initial
response with template defaults. For parameters with no default (like
`sixth_parameter`), the server returns `{valid: false, value: ""}`. On
first render, `useSyncFormParameters` sees this invalid server value and
overwrites the form's correctly-autofilled value ("99" from the previous
build) with "".
## Fix
When the server value is `{valid: false}`, preserve the current form
value instead of overwriting with "". This prevents the sync hook from
clobbering autofilled values before the server has had a chance to
process them.
## Verification
- TypeScript: zero type errors
- Biome lint: clean
- Unit tests: 2/2 passing
- **E2E soak test: 849/854 passed across 854 runs (99.5% pass rate)**
- 0 occurrences of the original flake (empty value on settings page)
- 5 residual failures are a separate pre-existing race in
`fillParameters` where user input is overwritten during the 500ms
debounce window
This commit is contained in:
@@ -27,16 +27,30 @@ export function useSyncFormParameters({
|
||||
useEffect(() => {
|
||||
if (!parameters) return;
|
||||
const currentFormValues = formValuesRef.current;
|
||||
|
||||
const newParameterValues = parameters.map((param) => ({
|
||||
name: param.name,
|
||||
value: param.value.valid ? param.value.value : "",
|
||||
}));
|
||||
|
||||
const currentFormValuesMap = new Map(
|
||||
currentFormValues.map((value) => [value.name, value.value]),
|
||||
);
|
||||
|
||||
const newParameterValues = parameters.map((param) => {
|
||||
// When the server value is not valid (e.g., the initial
|
||||
// WebSocket response before any user input is sent),
|
||||
// preserve the current form value. This prevents the sync
|
||||
// hook from overwriting autofilled values (from the
|
||||
// previous build) with empty strings before the server
|
||||
// has had a chance to process them.
|
||||
if (!param.value.valid) {
|
||||
const existingValue = currentFormValuesMap.get(param.name);
|
||||
if (existingValue !== undefined) {
|
||||
return { name: param.name, value: existingValue };
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: param.name,
|
||||
value: param.value.valid ? param.value.value : "",
|
||||
};
|
||||
});
|
||||
|
||||
const isChanged =
|
||||
currentFormValues.length !== newParameterValues.length ||
|
||||
newParameterValues.some(
|
||||
|
||||
Reference in New Issue
Block a user