Skip to content

Commit f8a2058

Browse files
feggeclaude
andcommitted
test: fix flaky extension tests with polling-based waits
Replace fixed setTimeout delays with a polling mechanism that waits for expected state changes. This resolves intermittent test failures in CI where 500ms delays were insufficient for VS Code operations to complete. - Add waitForCondition helper to poll for state changes with timeout - Update toggleAudited test to poll until audit state changes - Update addPartiallyAudited test to poll for state changes - Update showMarkedFilesDayLog test to poll for editor visibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 2e53c07 commit f8a2058

File tree

2 files changed

+65
-18
lines changed

2 files changed

+65
-18
lines changed

test/extension/suite/commands.test.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ function readWeauditData(workspaceFolder: vscode.WorkspaceFolder): SerializedDat
3232
return JSON.parse(content) as SerializedData;
3333
}
3434

35+
/**
36+
* Poll for a condition to become true, with timeout.
37+
* Returns true if condition was met, false if timed out.
38+
*/
39+
async function waitForCondition(condition: () => boolean, timeoutMs: number = 5000, pollIntervalMs: number = 100): Promise<boolean> {
40+
const startTime = Date.now();
41+
while (Date.now() - startTime < timeoutMs) {
42+
if (condition()) {
43+
return true;
44+
}
45+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
46+
}
47+
return condition(); // Final check
48+
}
49+
3550
suite("Command Execution", () => {
3651
const extensionId = "trailofbits.weaudit";
3752
let testFileUri: vscode.Uri;
@@ -207,21 +222,26 @@ suite("Command Execution", () => {
207222
// If not audited, mark it as audited
208223
if (!wasAudited) {
209224
await vscode.commands.executeCommand("weAudit.toggleAudited");
210-
await new Promise((resolve) => setTimeout(resolve, 500));
225+
// Wait for the audit to complete
226+
await waitForCondition(() => {
227+
const data = readWeauditData(workspaceFolder);
228+
return data?.auditedFiles.some((f) => f.path === relativePath) ?? false;
229+
});
211230
}
212231

213232
// Close all editors first to get a clean slate
214233
await vscode.commands.executeCommand("workbench.action.closeAllEditors");
215-
await new Promise((resolve) => setTimeout(resolve, 200));
234+
// Wait for editors to close
235+
await waitForCondition(() => vscode.window.visibleTextEditors.length === 0, 2000);
216236

217237
const editorsBefore = vscode.window.visibleTextEditors.length;
218238

219239
// Now call the day log command - it should open a markdown document
220240
await vscode.commands.executeCommand("weAudit.showMarkedFilesDayLog");
221-
await new Promise((resolve) => setTimeout(resolve, 500));
222241

223-
const editorsAfter = vscode.window.visibleTextEditors.length;
224-
assert.ok(editorsAfter > editorsBefore, "A new document should be opened with the day log");
242+
// Wait for a new editor to appear (poll instead of fixed delay)
243+
const editorOpened = await waitForCondition(() => vscode.window.visibleTextEditors.length > editorsBefore, 5000);
244+
assert.ok(editorOpened, "A new document should be opened with the day log");
225245

226246
// Verify the opened document contains expected content
227247
const activeEditor = vscode.window.activeTextEditor;
@@ -235,7 +255,11 @@ suite("Command Execution", () => {
235255
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
236256
await openTestFile();
237257
await vscode.commands.executeCommand("weAudit.toggleAudited");
238-
await new Promise((resolve) => setTimeout(resolve, 500));
258+
// Wait for the unaudit to complete
259+
await waitForCondition(() => {
260+
const data = readWeauditData(workspaceFolder);
261+
return !data?.auditedFiles.some((f) => f.path === relativePath);
262+
});
239263
}
240264
});
241265
});

test/extension/suite/decorations.test.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@ function readWeauditData(workspaceFolder: vscode.WorkspaceFolder): SerializedDat
2525
return JSON.parse(content) as SerializedData;
2626
}
2727

28+
/**
29+
* Poll for a condition to become true, with timeout.
30+
* Returns true if condition was met, false if timed out.
31+
*/
32+
async function waitForCondition(condition: () => boolean, timeoutMs: number = 5000, pollIntervalMs: number = 100): Promise<boolean> {
33+
const startTime = Date.now();
34+
while (Date.now() - startTime < timeoutMs) {
35+
if (condition()) {
36+
return true;
37+
}
38+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
39+
}
40+
return condition(); // Final check
41+
}
42+
2843
suite("Editor Decorations", () => {
2944
const extensionId = "trailofbits.weaudit";
3045
let testFileUri: vscode.Uri;
@@ -80,13 +95,15 @@ suite("Editor Decorations", () => {
8095

8196
// Toggle audited status
8297
await vscode.commands.executeCommand("weAudit.toggleAudited");
83-
await new Promise((resolve) => setTimeout(resolve, 500));
8498

85-
// Verify the state changed
86-
const dataAfter = readWeauditData(workspaceFolder);
87-
const isAudited = dataAfter?.auditedFiles.some((f) => f.path === relativePath) ?? false;
99+
// Wait for the state to actually change (poll instead of fixed delay)
100+
const stateChanged = await waitForCondition(() => {
101+
const dataAfter = readWeauditData(workspaceFolder);
102+
const isAudited = dataAfter?.auditedFiles.some((f) => f.path === relativePath) ?? false;
103+
return isAudited !== wasAudited;
104+
});
88105

89-
assert.notStrictEqual(isAudited, wasAudited, "Audited status should be toggled");
106+
assert.ok(stateChanged, "Audited status should be toggled");
90107
});
91108

92109
test("addPartiallyAudited command adds partially audited region", async function () {
@@ -100,7 +117,11 @@ suite("Editor Decorations", () => {
100117
const dataCheck = readWeauditData(workspaceFolder);
101118
if (dataCheck?.auditedFiles.some((f) => f.path === relativePath)) {
102119
await vscode.commands.executeCommand("weAudit.toggleAudited");
103-
await new Promise((resolve) => setTimeout(resolve, 500));
120+
// Wait for the unaudit to complete
121+
await waitForCondition(() => {
122+
const data = readWeauditData(workspaceFolder);
123+
return !data?.auditedFiles.some((f) => f.path === relativePath);
124+
});
104125
}
105126

106127
// Use lines that are not already partially audited
@@ -119,15 +140,17 @@ suite("Editor Decorations", () => {
119140

120141
// Add partially audited region
121142
await vscode.commands.executeCommand("weAudit.addPartiallyAudited");
122-
await new Promise((resolve) => setTimeout(resolve, 500));
123143

124-
// Verify state changed
125-
const dataAfter = readWeauditData(workspaceFolder);
126-
const regionExistsAfter =
127-
dataAfter?.partiallyAuditedFiles.some((f) => f.path === relativePath && f.startLine === startLine && f.endLine === endLine) ?? false;
144+
// Wait for the state to actually change (poll instead of fixed delay)
145+
const stateChanged = await waitForCondition(() => {
146+
const dataAfter = readWeauditData(workspaceFolder);
147+
const regionExistsAfter =
148+
dataAfter?.partiallyAuditedFiles.some((f) => f.path === relativePath && f.startLine === startLine && f.endLine === endLine) ?? false;
149+
return regionExistsAfter !== regionExistsBefore;
150+
});
128151

129152
// The command toggles the region - so if it existed before it's removed, if it didn't exist it's added
130-
assert.notStrictEqual(regionExistsAfter, regionExistsBefore, "Partially audited region should be toggled");
153+
assert.ok(stateChanged, "Partially audited region should be toggled");
131154
});
132155

133156
test("Extension survives file edits", async function () {

0 commit comments

Comments
 (0)