Mocks + full workflow scenario

This commit is contained in:
Osvaldo Ortega
2026-03-12 15:52:48 -07:00
parent a988b69b4e
commit 739fb0ae6f
6 changed files with 267 additions and 24 deletions

View File

@@ -99,9 +99,14 @@ export class SessionsTerminalContribution extends Disposable implements IWorkben
let existing = await this._findTerminalsForKey(key);
if (existing.length === 0) {
existing = [await this._terminalService.createTerminal({ config: { cwd } })];
this._terminalService.setActiveInstance(existing[0]);
this._logService.trace(`[SessionsTerminal] Created terminal ${existing[0].instanceId} for ${cwd.fsPath}`);
try {
existing = [await this._terminalService.createTerminal({ config: { cwd } })];
this._terminalService.setActiveInstance(existing[0]);
this._logService.trace(`[SessionsTerminal] Created terminal ${existing[0].instanceId} for ${cwd.fsPath}`);
} catch (e) {
this._logService.trace(`[SessionsTerminal] Cannot create terminal for ${cwd.fsPath}: ${e}`);
return [];
}
}
if (focus) {

View File

@@ -154,6 +154,7 @@ import './contrib/accountMenu/browser/account.contribution.js';
import './contrib/aiCustomizationTreeView/browser/aiCustomizationTreeView.contribution.js';
import './contrib/applyCommitsToParentRepo/browser/applyChangesToParentRepo.js';
import './contrib/chat/browser/chat.contribution.js';
import './contrib/terminal/browser/sessionsTerminalContribution.js';
import './contrib/sessions/browser/sessions.contribution.js';
import './contrib/sessions/browser/customizationsToolbar.contribution.js';
import './contrib/changes/browser/changesView.contribution.js';

View File

@@ -102,23 +102,6 @@ function registerMockAuth(vscode) {
function registerMockCommands(vscode) {
const disposables = [];
// Mock code review — returns canned review comments
disposables.push(vscode.commands.registerCommand(
'github.copilot.chat.codeReview.run',
(args) => {
console.log('[sessions-e2e-mock] Mock code review invoked', args);
const files = args?.files ?? [];
const comments = files.slice(0, 2).map((file, i) => ({
uri: file.currentUri,
range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 },
body: `Mock review comment ${i + 1}: Consider improving this code.`,
kind: 'suggestion',
severity: 'info',
}));
return { type: 'success', comments };
}
));
// Mock create PR — simulates successful PR creation
disposables.push(vscode.commands.registerCommand(
'github.copilot.chat.createPullRequestCopilotCLIAgentSession.createPR',
@@ -178,7 +161,7 @@ function registerMockCommands(vscode) {
'github.copilot.chat.updateCopilotCLIAgentSessionChanges.update',
() => {
console.log('[sessions-e2e-mock] Mock Update Changes invoked');
vscode.window.showInformationMessage('Mock: Changes updated successfully');
vscode.window.showInformationMessage('Mock: Changes updated successfully',);
}
));

View File

@@ -0,0 +1,25 @@
# Scenario: Full workflow
## Steps
1. Type "build the project" in the chat input
2. Press Enter to submit
3. Verify there is a response in the chat
4. Toggle the secondary side bar
5. Verify the changes view shows "CHANGES" with a badge
6. Verify "package.json" is visible in the changes list
7. Verify "build.ts" is visible in the changes list
8. Verify "index.ts" is visible in the changes list
9. Click on "index.ts" in the changes list
10. Verify a diff editor opens with modified content
11. Press Escape to close the diff editor
12. Verify "Merge" button is visible in changes view header
13. Verify the "Open Terminal" button is visible
14. Click the "Open Terminal" button
15. Verify the terminal panel becomes visible
16. Verify the terminal tab shows "session-1" in its label
17. Click "New Session" to create a new session
18. Type "fix the bug" in the chat input
19. Press Enter to submit
20. Verify the terminal tab label changes to show "session-2"
21. Click back on the first session in the sessions list
22. Verify the terminal tab label changes back to show "session-1"

View File

@@ -0,0 +1,140 @@
{
"scenario": "Scenario: Full workflow",
"generatedAt": "2026-03-12T22:48:50.725Z",
"steps": [
{
"description": "Type \"build the project\" in the chat input",
"commands": [
"click textbox \"Chat input\"",
"type \"build the project\""
]
},
{
"description": "Press Enter to submit",
"commands": [
"press Enter"
]
},
{
"description": "Verify there is a response in the chat",
"commands": [
"# ASSERT_VISIBLE: I'll help you build the project. Here are the changes:"
]
},
{
"description": "Toggle the secondary side bar",
"commands": [
"click button \"Toggle Secondary Side Bar Visibility\""
]
},
{
"description": "Verify the changes view shows \"CHANGES\" with a badge",
"commands": [
"# ASSERT_VISIBLE: Changes - 3 files changed"
]
},
{
"description": "Verify \"package.json\" is visible in the changes list",
"commands": [
"# ASSERT_VISIBLE: package.json"
]
},
{
"description": "Verify \"build.ts\" is visible in the changes list",
"commands": [
"# ASSERT_VISIBLE: build.ts"
]
},
{
"description": "Verify \"index.ts\" is visible in the changes list",
"commands": [
"# ASSERT_VISIBLE: index.ts"
]
},
{
"description": "Click on \"index.ts\" in the changes list",
"commands": [
"click treeitem \"index.ts\""
]
},
{
"description": "Verify a diff editor opens with modified content",
"commands": [
"# ASSERT_VISIBLE: index.ts"
]
},
{
"description": "Press Escape to close the diff editor",
"commands": [
"press Escape"
]
},
{
"description": "Verify \"Merge\" button is visible in changes view header",
"commands": [
"# ASSERT_VISIBLE: Merge"
]
},
{
"description": "Verify the \"Open Terminal\" button is visible",
"commands": [
"# ASSERT_VISIBLE: Open Terminal"
]
},
{
"description": "Click the \"Open Terminal\" button",
"commands": [
"click button \"Open Terminal\""
]
},
{
"description": "Verify the terminal panel becomes visible",
"commands": [
"# ASSERT_VISIBLE: Terminal"
]
},
{
"description": "Verify the terminal tab shows \"session-1\" in its label",
"commands": [
"# ASSERT_VISIBLE: bash - session-1"
]
},
{
"description": "Click \"New Session\" to create a new session",
"commands": [
"click button \"New Session\""
]
},
{
"description": "Type \"fix the bug\" in the chat input",
"commands": [
"click textbox \"Chat input\"",
"type \"fix the bug\""
]
},
{
"description": "Press Enter to submit",
"commands": [
"press Enter"
]
},
{
"description": "Verify the terminal tab label changes to show \"session-2\"",
"commands": [
"# ASSERT_VISIBLE: bash - session-2"
]
},
{
"description": "Click back on the first session in the sessions list",
"commands": [
"click listitem \"build the project\""
]
},
{
"description": "Verify the terminal tab label changes back to show \"session-1\"",
"commands": [
"# ASSERT_VISIBLE: bash - session-1"
]
}
]
}

View File

@@ -25,6 +25,10 @@ import { IChatProgress } from '../../workbench/contrib/chat/common/chatService/c
import { IChatSessionsService, IChatSessionItem, IChatSessionFileChange, ChatSessionStatus, IChatSessionHistoryItem } from '../../workbench/contrib/chat/common/chatSessionsService.js';
import { IGitService, IGitExtensionDelegate, IGitRepository } from '../../workbench/contrib/git/common/gitService.js';
import { IFileService } from '../../platform/files/common/files.js';
import { ITerminalService } from '../../workbench/contrib/terminal/browser/terminal.js';
import { ITerminalBackend, ITerminalBackendRegistry, IProcessReadyEvent, IProcessProperty, ProcessPropertyType, TerminalExtensions, ITerminalProcessOptions, IShellLaunchConfig } from '../../platform/terminal/common/terminal.js';
import { IProcessEnvironment } from '../../base/common/platform.js';
import { Registry } from '../../platform/registry/common/platform.js';
import { InMemoryFileSystemProvider } from '../../platform/files/common/inMemoryFilesystemProvider.js';
import { VSBuffer } from '../../base/common/buffer.js';
@@ -230,16 +234,19 @@ class MockChatAgentContribution extends Disposable implements IWorkbenchContribu
private readonly _sessionItems: IChatSessionItem[] = [];
private readonly _itemsChangedEmitter = new Emitter<void>();
private readonly _sessionHistory = new Map<string, IChatSessionHistoryItem[]>();
private _worktreeCounter = 0;
constructor(
@IChatAgentService private readonly chatAgentService: IChatAgentService,
@IStorageService private readonly storageService: IStorageService,
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
@ITerminalService private readonly terminalService: ITerminalService,
) {
super();
this._register(this._itemsChangedEmitter);
this.registerMockAgents();
this.registerMockSessionProvider();
this.registerMockTerminalBackend();
this.preseedFolder();
}
@@ -285,6 +292,7 @@ class MockChatAgentContribution extends Disposable implements IWorkbenchContribu
label: message.slice(0, 50) || 'Mock Session',
status: ChatSessionStatus.Completed,
timing: { created: now, lastRequestStarted: now, lastRequestEnded: now },
metadata: { worktreePath: `/mock-worktrees/session-${++this._worktreeCounter}` },
...(changes ? { changes } : {}),
});
}
@@ -380,11 +388,15 @@ class MockChatAgentContribution extends Disposable implements IWorkbenchContribu
},
}));
// Register an item controller so sessions appear in the sidebar list
const items = this._sessionItems;
// Register an item controller so sessions appear in the sidebar list.
// Only copilotcli (Background) sessions need real items — the
// copilot-cloud-agent controller must return an empty array to
// prevent it from overwriting sessions with the wrong providerType
// during a full model resolve.
const controllerItems = scheme === 'copilotcli' ? this._sessionItems : [];
this._register(this.chatSessionsService.registerChatSessionItemController(scheme, {
onDidChangeChatSessionItems: this._itemsChangedEmitter.event,
get items() { return items; },
get items() { return controllerItems; },
async refresh() { /* in-memory, no-op */ },
}));
@@ -395,6 +407,83 @@ class MockChatAgentContribution extends Disposable implements IWorkbenchContribu
}
}
private registerMockTerminalBackend(): void {
const terminalService = this.terminalService;
const backend = this.createMockTerminalBackend();
Registry.as<ITerminalBackendRegistry>(TerminalExtensions.Backend).registerTerminalBackend(backend);
terminalService.registerProcessSupport(true);
console.log('[Sessions Web Test] Registered mock terminal backend');
}
private createMockTerminalBackend(): ITerminalBackend {
return {
remoteAuthority: undefined,
isVirtualProcess: false,
onDidRequestDetach: Event.None,
attachToProcess: async () => { throw new Error('Not supported'); },
attachToRevivedProcess: async () => { throw new Error('Not supported'); },
listProcesses: async () => [],
getProfiles: async () => [],
getDefaultProfile: async () => undefined,
getDefaultSystemShell: async () => '/bin/mock-shell',
getShellEnvironment: async () => ({}),
setTerminalLayoutInfo: async () => { },
getTerminalLayoutInfo: async () => undefined,
reduceConnectionGraceTime: () => { },
requestDetachInstance: () => { },
acceptDetachInstanceReply: () => { },
persistTerminalState: () => { },
createProcess: async (_shellLaunchConfig: IShellLaunchConfig, _cwd: string | URI, _cols: number, _rows: number, _unicodeVersion: string, _env: IProcessEnvironment, _options: ITerminalProcessOptions, _shouldPersist: boolean) => {
const onProcessData = new Emitter<string>();
const onProcessReady = new Emitter<IProcessReadyEvent>();
const onProcessExit = new Emitter<number | undefined>();
const onDidChangeHasChildProcesses = new Emitter<boolean>();
const onDidChangeProperty = new Emitter<IProcessProperty<ProcessPropertyType>>();
// Resolve cwd from createProcess arg or shellLaunchConfig
const rawCwd = _cwd || _shellLaunchConfig.cwd;
const cwd = !rawCwd ? '/' : typeof rawCwd === 'string' ? rawCwd : rawCwd.path;
console.log(`[Sessions Web Test] Mock terminal createProcess cwd: '${cwd}' (raw _cwd: '${_cwd}', slc.cwd: '${_shellLaunchConfig.cwd}')`);
// Fire ready after a microtask so the terminal service can wire up listeners
setTimeout(() => {
onProcessReady.fire({ pid: 1, cwd, windowsPty: undefined });
}, 0);
return {
id: 0,
shouldPersist: false,
onProcessData: onProcessData.event,
onProcessReady: onProcessReady.event,
onDidChangeHasChildProcesses: onDidChangeHasChildProcesses.event,
onDidChangeProperty: onDidChangeProperty.event,
onProcessExit: onProcessExit.event,
start: async () => undefined,
shutdown: async () => { },
input: async () => { },
resize: () => { },
clearBuffer: () => { },
acknowledgeDataEvent: () => { },
setUnicodeVersion: async () => { },
getInitialCwd: async () => cwd,
getCwd: async () => cwd,
getLatency: async () => [],
processBinary: async () => { },
refreshProperty: async (property: ProcessPropertyType) => { throw new Error(`Not supported: ${property}`); },
updateProperty: async () => { },
clearUnrespondedRequest: () => { },
};
},
getWslPath: async (original: string, _direction: 'unix-to-win' | 'win-to-unix') => original,
getEnvironment: async () => ({}),
getPerformanceMarks: () => [],
onPtyHostUnresponsive: Event.None,
onPtyHostResponsive: Event.None,
onPtyHostRestart: Event.None,
onPtyHostConnected: Event.None,
} as unknown as ITerminalBackend;
}
private preseedFolder(): void {
const mockFolderUri = URI.from({ scheme: 'mock-fs', authority: 'mock-repo', path: '/mock-repo' }).toString();
this.storageService.store('agentSessions.lastPickedFolder', mockFolderUri, StorageScope.PROFILE, StorageTarget.MACHINE);