Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions media/webview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -64,7 +64,7 @@
"altText": "TestDriver API key setup"
},
"completionEvents": [
"command:testdriver.setApiKey"
"onContext:testdriver.hasApiKey"
]
},
{
Expand All @@ -76,7 +76,7 @@
"altText": "TestDriver chat interface"
},
"completionEvents": [
"command:testdriver.openChat"
"onContext:testdriver.chatOpened"
]
},
{
Expand All @@ -88,7 +88,7 @@
"altText": "TestDriver run test interface"
},
"completionEvents": [
"command:testdriver.runTest"
"onContext:testdriver.testPanelOpened"
]
},
{
Expand Down
8 changes: 8 additions & 0 deletions src/commands/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions src/commands/runTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
57 changes: 55 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);
Expand Down
40 changes: 36 additions & 4 deletions src/utils/sidebarProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) {
Expand All @@ -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
});
}
}
Expand Down Expand Up @@ -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;
}

Expand All @@ -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');
Expand Down
Loading