Try fix Copilot CLI editor flake (#322722)

* Try fix Copilot CLI editor flake

* Smoke test probes

* Remove probe
This commit is contained in:
Alex Ross
2026-06-24 17:56:43 +02:00
committed by GitHub
parent 50b7e69ba3
commit 71292ea46e
2 changed files with 45 additions and 1 deletions
@@ -160,6 +160,12 @@ export class ChatEditor extends AbstractEditorWithViewState<IChatEditorViewState
override clearInput(): void {
this.widget.setModel(undefined);
// Clear the bound-resource attribute while the rebind is in flight so
// test automation can wait for the next `updateModel` cycle to finish
// before acting on the editor.
if (this._editorContainer) {
delete this._editorContainer.dataset.boundChatResource;
}
super.clearInput();
}
@@ -278,6 +284,14 @@ export class ChatEditor extends AbstractEditorWithViewState<IChatEditorViewState
private updateModel(model: IChatModel): void {
this.widget.setModel(model);
// Expose the bound chat resource on the DOM so test automation can
// synchronize with the post-rebind state without polling timeouts.
// Set AFTER `setModel` so observers see the attribute only once the
// widget is fully attached to the loaded model. Mirrors the same
// signal exposed by the Agents Window's `ChatView`.
if (this._editorContainer) {
this._editorContainer.dataset.boundChatResource = model.sessionResource.toString();
}
}
protected computeEditorViewState(_resource: URI): IChatEditorViewState | undefined {
+31 -1
View File
@@ -119,7 +119,37 @@ export class Chat {
}
async waitForEditorResponseText(predicate: string | RegExp, timeoutMs: number = 60_000): Promise<string> {
return await this.pollForResponseText(CHAT_EDITOR_RESPONSE, CHAT_EDITOR_RESPONSE_RENDERED, predicate, timeoutMs);
const matched = await this.pollForResponseText(CHAT_EDITOR_RESPONSE, CHAT_EDITOR_RESPONSE_RENDERED, predicate, timeoutMs);
// After a contributed chat session (e.g. Copilot CLI, Claude) returns
// its first response, the workbench commits the untitled session into
// a real (titled) one and `replaceEditors` swaps the chat editor over.
// If a follow-up message is sent during that swap, the participant
// request can be cancelled mid-flight by the editor swap. The
// {@link ChatEditor} sets `data-bound-chat-resource` on its editor
// container after binding its widget to the loaded chat model, and
// clears it during rebind — so we can wait for the swap to land via
// Playwright's push-based `waitForSelector` (no polling on our side).
await this.waitForEditorChatBoundToCommittedResource();
return matched;
}
/**
* Wait until the chat editor's root container advertises a non-untitled
* chat resource via `data-bound-chat-resource`. Uses Playwright's
* `page.waitForSelector` which is push-based (MutationObserver in the
* renderer). Soft no-op when the selector never matches within
* `timeoutMs` (e.g. for local sessions that don't go through the
* untitled → committed swap).
*/
private async waitForEditorChatBoundToCommittedResource(timeoutMs: number = 15_000): Promise<void> {
const selector = `.editor-instance .chat-editor-relative[data-bound-chat-resource]:not([data-bound-chat-resource*="/untitled-"])`;
try {
await this.code.driver.waitForElement(selector, { state: 'attached', timeout: timeoutMs });
} catch {
// Soft failure: caller already verified the response text is on
// screen, so proceed and let downstream assertions surface any
// actual problem.
}
}
private async pollForResponseText(bubbleSelector: string, renderedSelector: string, predicate: string | RegExp, timeoutMs: number): Promise<string> {