diff --git a/apps/desktop/e2e/slash-command-menu.spec.ts b/apps/desktop/e2e/slash-command-menu.spec.ts index 373c6c2482..7aae5a5828 100644 --- a/apps/desktop/e2e/slash-command-menu.spec.ts +++ b/apps/desktop/e2e/slash-command-menu.spec.ts @@ -78,7 +78,14 @@ test('compacts the active session', async ({ await expect.poll(() => composer.textContent()).toBe(''); await expect(page.getByText('压缩失败')).toHaveCount(0); - await composer.fill('after compact'); + // After the compact completes the composer clears and can remount. `fill()` + // can land before the contentEditable is focused again, so the draft never + // populates and Enter submits nothing — the flake in issue #3289. Type + // through the focused element and require the draft to have settled before + // dispatching, mirroring the running-turn spec below. + await composer.click(); + await composer.pressSequentially('after compact'); + await expect.poll(() => composer.textContent()).toBe('after compact'); await composer.press('Enter'); await expect(page.getByText('Fake backend received: after compact')).toBeVisible(); await expect(page.getByText('Fake backend received: /compact')).toHaveCount(0); diff --git a/packages/computer-use/src/__tests__/maka-cu-service.test.ts b/packages/computer-use/src/__tests__/maka-cu-service.test.ts index 89990f42ba..65b35bc34e 100644 --- a/packages/computer-use/src/__tests__/maka-cu-service.test.ts +++ b/packages/computer-use/src/__tests__/maka-cu-service.test.ts @@ -113,6 +113,7 @@ process.stdin.on('data', function (chunk) { let workDir = ''; let mockPath = ''; const services: MakaCuService[] = []; +const imageDirs: string[] = []; function delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -197,6 +198,7 @@ function makeService( }; const service = new MakaCuService(options); services.push(service); + imageDirs.push(imageDir); return { service, logPath, imageDir }; } @@ -215,7 +217,24 @@ after(async () => { // A disposed service disposing again is not a test failure. } } - if (workDir) await rm(workDir, { recursive: true, force: true }); + // `dispose()` is deliberately fire-and-forget: it SIGTERMs the child and + // schedules an asynchronous image-directory purge on child exit (or on the + // shutdown-grace SIGKILL, at most `shutdownGraceMs` = 3s later). Deleting + // `workDir` while those purges — and a child still flushing its ndjson log — + // are in flight races `rm` against concurrent writers and fails with + // ENOTEMPTY (issue #3290). Every `makeService` image directory is purged by + // its `dispose()`, so waiting for them to vanish is a "children exited and + // purges finished" barrier. The wait is tolerant rather than asserting — + // purge failures are permitted by contract ("the next spawn purges it + // again") and are not what this teardown tests — and the retrying `rm` + // backstop then only absorbs stragglers, not an unbounded race. + const purgeDeadline = Date.now() + 10_000; + while (imageDirs.some((imageDir) => existsSync(imageDir)) && Date.now() < purgeDeadline) { + await delay(25); + } + if (workDir) { + await rm(workDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); + } }); describe('maka-cu supervisor: what the child may put on stdout', () => {