diff --git a/media/webview.js b/media/webview.js index 8825041..5a2e339 100644 --- a/media/webview.js +++ b/media/webview.js @@ -227,6 +227,9 @@ class TestDriverWebview { case 'showSuggestedPromptsAfterExample': this.showSuggestedPromptsAfterExample(); break; + case 'showNoWorkspaceMessage': + this.showNoWorkspaceMessage(); + break; case 'error': this.addMessage(message.data, 'error', '❌'); this.isRunning = false; @@ -684,6 +687,20 @@ class TestDriverWebview { } } + showNoWorkspaceMessage() { + // Clear any existing messages + this.clearChat(); + + // Show a message indicating no workspace is available + this.addMessage('📁 No workspace folder is open', 'system', '📁'); + this.addMessage('Please open a folder to get started with TestDriver. Go to File → Open Folder... to select a project folder.', 'system', 'â„šī¸'); + + // Ensure empty state is hidden since we're showing messages + if (this.emptyState) { + this.emptyState.style.display = 'none'; + } + } + showSuggestedPromptsAfterExample() { // Set flag to show suggested prompts and keep them visible until first user message this.showSuggestedAfterExample = true; diff --git a/package.json b/package.json index bf52b49..7f4dfa1 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ { "id": "signup", "title": "Get a TestDriver API Key", - "description": "Because TestDriver is powered by agentic users, a free account is required to access:\n\n - Dedicated Virtual Machines (VMs) for running agents\n- TestDriver AI + Vision models\n- Community support\n[Get API Key](https://app.testdriver.ai/)", + "description": "An account is required to access TestDriver's AI Model as well as sandbox environments for running tests..\n[Get API Key](https://app.testdriver.ai/)", "media": { "image": "media/main.png", "altText": "TestDriver main interface" @@ -64,7 +64,7 @@ "altText": "TestDriver API key setup" }, "completionEvents": [ - "command:testdriver.setApiKey" + "onContext:testdriver.hasApiKey" ] }, { @@ -76,7 +76,7 @@ "altText": "TestDriver chat interface" }, "completionEvents": [ - "command:testdriver.openChat" + "onContext:testdriver.chatOpened" ] }, { @@ -88,7 +88,7 @@ "altText": "TestDriver run test interface" }, "completionEvents": [ - "command:testdriver.runTest" + "onContext:testdriver.testPanelOpened" ] }, { diff --git a/src/commands/chat.ts b/src/commands/chat.ts index b0d360c..9def9c0 100644 --- a/src/commands/chat.ts +++ b/src/commands/chat.ts @@ -313,6 +313,14 @@ export function registerChatCommand(context: vscode.ExtensionContext) { // Focus on the TestDriver sidebar view instead of creating a separate webview panel await vscode.commands.executeCommand('testdriver-sidebar.focus'); + + // Set context to indicate chat has been opened for walkthrough completion + await vscode.commands.executeCommand('setContext', 'testdriver.chatOpened', true); + + // Persist this state for future sessions + await context.globalState.update('testdriver.chatOpenedBefore', true); + + return true; // Return success for walkthrough completion }); context.subscriptions.push(disposable); diff --git a/src/commands/runTest.ts b/src/commands/runTest.ts index dd82609..efa1d64 100644 --- a/src/commands/runTest.ts +++ b/src/commands/runTest.ts @@ -10,6 +10,14 @@ export function registerRunTestCommand(context: vscode.ExtensionContext) { const disposable = vscode.commands.registerCommand('testdriver.runTest', async (_uri?: vscode.Uri) => { // Open the VS Code Test Explorer panel await vscode.commands.executeCommand('workbench.view.testing.focus'); + + // Set context to indicate test panel has been opened for walkthrough completion + await vscode.commands.executeCommand('setContext', 'testdriver.testPanelOpened', true); + + // Persist this state for future sessions + await context.globalState.update('testdriver.testPanelOpenedBefore', true); + + return true; // Return success for walkthrough completion }); context.subscriptions.push(disposable); } diff --git a/src/extension.ts b/src/extension.ts index 9a8f647..0edeb0a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -68,6 +68,33 @@ export async function activate(context: vscode.ExtensionContext) { registerTestdriverRunTest(context); registerTestdriverChat(context); + // Check if API key is already set and mark walkthrough step as complete if needed + const existingApiKey = await context.secrets.get('TD_API_KEY'); + if (existingApiKey) { + logger.info('API key already exists, walkthrough step should be complete'); + vscode.commands.executeCommand('setContext', 'testdriver.hasApiKey', true); + } else { + vscode.commands.executeCommand('setContext', 'testdriver.hasApiKey', false); + } + + // Check if chat has been opened before (for returning users) + const chatOpenedBefore = context.globalState.get('testdriver.chatOpenedBefore', false); + if (chatOpenedBefore || !isFirstInstall) { + // If not first install, assume chat has been opened before + vscode.commands.executeCommand('setContext', 'testdriver.chatOpened', true); + } else { + vscode.commands.executeCommand('setContext', 'testdriver.chatOpened', false); + } + + // Check if test panel has been opened before (for returning users) + const testPanelOpenedBefore = context.globalState.get('testdriver.testPanelOpenedBefore', false); + if (testPanelOpenedBefore || !isFirstInstall) { + // If not first install, assume test panel has been opened before + vscode.commands.executeCommand('setContext', 'testdriver.testPanelOpened', true); + } else { + vscode.commands.executeCommand('setContext', 'testdriver.testPanelOpened', false); + } + // Register the sidebar provider const sidebarProvider = new TestDriverSidebarProvider(context.extensionUri, context); context.subscriptions.push( @@ -110,16 +137,42 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(disposable); const apiKeyDisposable = vscode.commands.registerCommand('testdriver.setApiKey', async () => { + logger.info('API key command started'); + + // Check if API key is already set + const existingKey = await context.secrets.get('TD_API_KEY'); + if (existingKey) { + const overwrite = await vscode.window.showQuickPick( + ['Yes, replace it', 'No, keep existing'], + { + placeHolder: 'API key is already set. Do you want to replace it?' + } + ); + if (overwrite !== 'Yes, replace it') { + logger.info('API key command cancelled - keeping existing key'); + return true; // Consider this a successful completion since key exists + } + } + const apiKey = await vscode.window.showInputBox({ prompt: 'Enter your TestDriver API key (from app.testdriver.ai/team)', ignoreFocusOut: true, password: true }); - if (apiKey) { - await context.secrets.store('TD_API_KEY', apiKey); + if (apiKey && apiKey.trim()) { + await context.secrets.store('TD_API_KEY', apiKey.trim()); vscode.window.showInformationMessage('TestDriver API key saved securely.'); + logger.info('API key saved successfully'); + track({ event: 'api_key.set' }); + + // Set context to indicate API key is now available + await vscode.commands.executeCommand('setContext', 'testdriver.hasApiKey', true); + + return true; // Return success for walkthrough completion } else { vscode.window.showWarningMessage('No API key entered.'); + logger.info('API key command cancelled'); + return false; // Return failure } }); context.subscriptions.push(apiKeyDisposable); diff --git a/src/utils/sidebarProvider.ts b/src/utils/sidebarProvider.ts index 029c4af..bdcf621 100644 --- a/src/utils/sidebarProvider.ts +++ b/src/utils/sidebarProvider.ts @@ -71,10 +71,24 @@ export class TestDriverSidebarProvider implements vscode.WebviewViewProvider { if (workspaceFolder) { const fileName = editor.document.uri.fsPath.split('/').pop() || 'test file'; this._updateFileIndicator(workspaceFolder.name, fileName); + } else { + // Handle case where workspace folder is not defined + const fileName = editor.document.uri.fsPath.split('/').pop() || 'test file'; + this._updateFileIndicator('Unknown Workspace', fileName); } } }) ); + + // Listen for workspace folder changes to refresh the UI + this._context.subscriptions.push( + vscode.workspace.onDidChangeWorkspaceFolders(async () => { + console.log('Workspace folders changed, refreshing sidebar'); + if (this._view) { + await this._checkAndShowExamples(this._view); + } + }) + ); } public postMessage(message: { command: string; data: string }) { @@ -89,10 +103,14 @@ export class TestDriverSidebarProvider implements vscode.WebviewViewProvider { private _updateFileIndicator(workspaceName: string, fileName: string) { if (this._view) { + // Provide fallback values for safety + const safeworkspaceName = workspaceName || 'Unknown Workspace'; + const safeFileName = fileName || 'No file selected'; + this._view.webview.postMessage({ command: 'updateFileIndicator', - workspaceName: workspaceName, - fileName: fileName + workspaceName: safeworkspaceName, + fileName: safeFileName }); } } @@ -398,7 +416,21 @@ export class TestDriverSidebarProvider implements vscode.WebviewViewProvider { const targetWorkspaceFolder = await this._getTargetWorkspaceFolder(); if (!targetWorkspaceFolder) { - console.log('No workspace folders found'); + console.log('No workspace folders found, showing default state'); + + // Handle case when no workspace is available + this._updateFileIndicator('No Workspace', 'Open a folder to get started'); + + // Show a message to the user about opening a workspace + webviewView.webview.postMessage({ + command: 'showNoWorkspaceMessage' + }); + + // Hide both the input area and run button when no workspace + webviewView.webview.postMessage({ + command: 'hideInputAndRunButton' + }); + return; } @@ -423,7 +455,7 @@ export class TestDriverSidebarProvider implements vscode.WebviewViewProvider { const workspaceFolders = vscode.workspace.workspaceFolders; const isExtensionDev = workspaceFolders?.some(folder => folder.uri.fsPath.includes('testdriver-vscode-extension') - ); + ) ?? false; if (isExtensionDev && !showExamples) { console.log('Extension development mode detected, showing examples for testing');