Skip to content
Open
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
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2688,6 +2688,11 @@
"description": "File where the 'BrightScript Extension' output panel (i.e. debug logs for the extension) will be appended. If omitted, no file logging will be done. ${workspaceFolder} is supported and will point to the first workspace found.",
"scope": "resource"
}
},
"brightscript.screenshotDir": {
"type": "string",
"description": "Directory path where screenshots will be saved. If omitted, screenshots will be saved to the OS's temporary directory. Supports absolute paths (e.g., \"/Users/username/Desktop\" on macOS, \"C:\\\\Users\\\\username\\\\Desktop\" on Windows) and relative paths (e.g., \"debug/screenshots\" or \"debug\\\\screenshots\" on Windows) which are resolved relative to the workspace folder. ${workspaceFolder} is also supported and will point to the first workspace found.",
"scope": "resource"
}
}
],
Expand Down
169 changes: 169 additions & 0 deletions src/commands/CaptureScreenshotCommand.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { vscode } from '../mockVscode.spec';
import { createSandbox } from 'sinon';
import { CaptureScreenshotCommand } from './CaptureScreenshotCommand';
import { BrightScriptCommands } from '../BrightScriptCommands';
import * as rokuDeploy from 'roku-deploy';
import { expect } from 'chai';
import URI from 'vscode-uri';
import { standardizePath as s } from 'brighterscript';

const cwd = s`${process.cwd()}`;

const sinon = createSandbox();

describe('CaptureScreenshotCommand', () => {
let brightScriptCommands: BrightScriptCommands;
let command: CaptureScreenshotCommand;
let context = vscode.context;
let workspace = vscode.workspace;

beforeEach(() => {
command = new CaptureScreenshotCommand();
brightScriptCommands = new BrightScriptCommands({} as any, {} as any, {} as any, {} as any, {} as any, {} as any);
command.register(context, brightScriptCommands);
});

afterEach(() => {
sinon.restore();
workspace.workspaceFolders = [];
});

it('gets remoteHost and remotePassword when hostParam is not provided', async () => {
sinon.stub(brightScriptCommands, 'getRemoteHost').resolves('1.1.1.1');
sinon.stub(brightScriptCommands, 'getRemotePassword').resolves('password');

const { host, password } = await command['getHostAndPassword']();

expect(host).to.eql('1.1.1.1');
expect(password).to.eql('password');
});

it('gets remotePassword when hostParam matches remoteHost', async () => {
await context.workspaceState.update('remoteHost', '1.1.1.1');
await context.workspaceState.update('remotePassword', 'password');

const { host, password } = await command['getHostAndPassword']('1.1.1.1');

expect(host).to.eql('1.1.1.1');
expect(password).to.eql('password');
});

it('prompts for password when hostParam does not match remoteHost', async () => {
sinon.stub(vscode.window, 'showInputBox').resolves('password');

const { host, password } = await command['getHostAndPassword']('1.1.1.1');

expect(host).to.eql('1.1.1.1');
expect(password).to.eql('password');
});

it('shows error message when captureScreenshot fails', async () => {
const stub = sinon.stub(command as any, 'getHostAndPassword').callsFake(() => Promise.resolve({ host: '1.1.1.1', password: 'password' }));
sinon.stub(rokuDeploy, 'takeScreenshot').rejects(new Error('Screenshot failed'));
const stubError = sinon.stub(vscode.window, 'showErrorMessage');

await command['captureScreenshot']('1.1.1.1');

expect(stub.getCall(0).args[0]).to.eql('1.1.1.1');
expect(stubError.calledOnce).to.be.true;
});

it('uses temp dir when screenshotDir is not defined', async () => {
sinon.stub(command as any, 'getHostAndPassword').callsFake(() => Promise.resolve({ host: '1.1.1.1', password: 'password' }));
const stub = sinon.stub(rokuDeploy, 'takeScreenshot').returns(Promise.resolve('screenshot.png'));

await command['captureScreenshot']();

expect(stub.getCall(0).args[0]).to.eql({ host: '1.1.1.1', password: 'password' });
});

it('uses screenshotDir with single workspace', async () => {
sinon.stub(command as any, 'getHostAndPassword').callsFake(() => Promise.resolve({ host: '1.1.1.1', password: 'password' }));
const stub = sinon.stub(rokuDeploy, 'takeScreenshot').returns(Promise.resolve('screenshot.png'));
workspace._configuration = {
'brightscript.screenshotDir': '${workspaceFolder}/screenshots'
};
workspace.workspaceFolders = [
{
uri: URI.file(s`${cwd}/workspace`),
name: 'test-workspace',
index: 0
}
];

await command['captureScreenshot']();

expect(stub.getCall(0).args[0]).to.eql({ host: '1.1.1.1', password: 'password', outDir: s`${cwd}/workspace/screenshots` });
});

it('uses relative screenshotDir with single workspace', async () => {
sinon.stub(command as any, 'getHostAndPassword').callsFake(() => Promise.resolve({ host: '1.1.1.1', password: 'password' }));
const stub = sinon.stub(rokuDeploy, 'takeScreenshot').returns(Promise.resolve('screenshot.png'));
workspace._configuration = {
'brightscript.screenshotDir': 'screenshots'
};
workspace.workspaceFolders = [
{
uri: URI.file(s`${cwd}/workspace`),
name: 'test-workspace',
index: 0
}
];

await command['captureScreenshot']();

expect(stub.getCall(0).args[0]).to.eql({ host: '1.1.1.1', password: 'password', outDir: s`${cwd}/workspace/screenshots` });
});

it('uses screenshotDir with multiple workspace', async () => {
sinon.stub(command as any, 'getHostAndPassword').callsFake(() => Promise.resolve({ host: '1.1.1.1', password: 'password' }));
const stub = sinon.stub(rokuDeploy, 'takeScreenshot').returns(Promise.resolve('screenshot.png'));
const workspaceFolders = [
{
uri: URI.file(s`${cwd}/workspace1`),
name: 'test-workspace',
index: 0
},
{
uri: URI.file(s`${cwd}/workspace2`),
name: 'test-workspace2',
index: 1
}
];
workspace.workspaceFolders = workspaceFolders;
workspace._configuration = {
'brightscript.screenshotDir': '${workspaceFolder}/screenshots'
};
sinon.stub(vscode.window, 'showWorkspaceFolderPick').resolves(workspaceFolders[1]);

await command['captureScreenshot']();

expect(stub.getCall(0).args[0]).to.eql({ host: '1.1.1.1', password: 'password', outDir: s`${cwd}/workspace2/screenshots` });
});

it('uses relative screenshotDir with multiple workspace', async () => {
sinon.stub(command as any, 'getHostAndPassword').callsFake(() => Promise.resolve({ host: '1.1.1.1', password: 'password' }));
const stub = sinon.stub(rokuDeploy, 'takeScreenshot').returns(Promise.resolve('screenshot.png'));
const workspaceFolders = [
{
uri: URI.file(s`${cwd}/workspace1`),
name: 'test-workspace',
index: 0
},
{
uri: URI.file(s`${cwd}/workspace2`),
name: 'test-workspace2',
index: 1
}
];
workspace.workspaceFolders = workspaceFolders;
workspace._configuration = {
'brightscript.screenshotDir': 'screenshots'
};
sinon.stub(vscode.window, 'showWorkspaceFolderPick').resolves(workspaceFolders[1]);

await command['captureScreenshot']();

expect(stub.getCall(0).args[0]).to.eql({ host: '1.1.1.1', password: 'password', outDir: s`${cwd}/workspace2/screenshots` });
});
});
98 changes: 63 additions & 35 deletions src/commands/CaptureScreenshotCommand.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,81 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as rokuDeploy from 'roku-deploy';
import type { BrightScriptCommands } from '../BrightScriptCommands';

export const FILE_SCHEME = 'bs-captureScreenshot';

export class CaptureScreenshotCommand {
private context: vscode.ExtensionContext;
private brightScriptCommands: BrightScriptCommands;

public register(context: vscode.ExtensionContext, BrightScriptCommandsInstance: BrightScriptCommands) {
context.subscriptions.push(vscode.commands.registerCommand('extension.brightscript.captureScreenshot', async (hostParam?: string) => {
let host: string;
let password: string;
public register(context: vscode.ExtensionContext, brightScriptCommands: BrightScriptCommands) {
this.context = context;
this.brightScriptCommands = brightScriptCommands;
context.subscriptions.push(vscode.commands.registerCommand('extension.brightscript.captureScreenshot', this.captureScreenshot.bind(this)));
}

private async getHostAndPassword(hostParam?: string): Promise<{ host: string; password: string }> {
let host: string;
let password: string;

//if a hostParam was not provided, then go the normal flow for getting info
if (!hostParam) {
host = await BrightScriptCommandsInstance.getRemoteHost();
password = await BrightScriptCommandsInstance.getRemotePassword();
//if a hostParam was not provided, then go the normal flow for getting info
if (!hostParam) {
host = await this.brightScriptCommands.getRemoteHost();
password = await this.brightScriptCommands.getRemotePassword();

//the host was provided, probably by clicking the "capture screenshot" link in the tree view. Do we have a password stored as well? If not, prompt for one
//the host was provided, probably by clicking the "capture screenshot" link in the tree view. Do we have a password stored as well? If not, prompt for one
} else {
host = hostParam;
let remoteHost = await this.context.workspaceState.get('remoteHost');
if (host === remoteHost) {
password = this.context.workspaceState.get('remotePassword');
} else {
host = hostParam;
let remoteHost = await context.workspaceState.get('remoteHost');
if (host === remoteHost) {
password = context.workspaceState.get('remotePassword');
} else {
password = await vscode.window.showInputBox({
placeHolder: `Please enter the developer password for host '${host}'`,
value: ''
});
}
password = await vscode.window.showInputBox({
placeHolder: `Please enter the developer password for host '${host}'`,
value: ''
});
}
}

return { host: host, password: password };
}

await vscode.window.withProgress({
title: `Capturing screenshot from '${host}'`,
location: vscode.ProgressLocation.Notification
}, async () => {
try {
let screenshotPath = await rokuDeploy.takeScreenshot({
host: host,
password: password
});
if (screenshotPath) {
void vscode.window.showInformationMessage(`Screenshot saved at: ` + screenshotPath);
void vscode.commands.executeCommand('vscode.open', vscode.Uri.file(screenshotPath));
private async captureScreenshot(hostParam?: string) {
const { host, password } = await this.getHostAndPassword(hostParam);

await vscode.window.withProgress({
title: `Capturing screenshot from '${host}'`,
location: vscode.ProgressLocation.Notification
}, async () => {
try {
let screenshotDir = vscode.workspace.getConfiguration('brightscript').get<string>('screenshotDir');
if (screenshotDir) {
let workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
if (vscode.workspace.workspaceFolders?.length > 1) {
const workspaceFolder = await vscode.window.showWorkspaceFolderPick();
if (workspaceFolder) {
workspacePath = workspaceFolder.uri.fsPath;
}
}
} catch (e) {
void vscode.window.showErrorMessage('Could not capture screenshot');

screenshotDir = screenshotDir.replace('${workspaceFolder}', workspacePath);
screenshotDir = path.resolve(workspacePath ?? process.cwd(), screenshotDir);
}
});
}));

let screenshotPath = await rokuDeploy.takeScreenshot({
host: host,
password: password,
...(screenshotDir && { outDir: screenshotDir })
});
if (screenshotPath) {
void vscode.window.showInformationMessage(`Screenshot saved at: ` + screenshotPath);
void vscode.commands.executeCommand('vscode.open', vscode.Uri.file(screenshotPath));
}
} catch (e) {
void vscode.window.showErrorMessage('Could not capture screenshot');
}
});
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/mockVscode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ afterEach(() => {
delete vscode.workspace.workspaceFile;
delete vscode.workspace._configuration;
vscode.context.globalState['_data'] = {};
vscode.context.workspaceState['_data'] = {};
});

export let vscode = {
Expand Down Expand Up @@ -172,7 +173,9 @@ export let vscode = {
withProgress: (options, action) => {
return action();
},
showInputBox: () => { },
showInputBox: () => {
return Promise.resolve(undefined);
},
createStatusBarItem: () => {
return {
clear: () => { },
Expand Down Expand Up @@ -252,6 +255,9 @@ export let vscode = {
},
showOpenDialog: () => {
return Promise.resolve([]);
},
showWorkspaceFolderPick: () => {
return Promise.resolve(undefined);
}
},
CompletionItemKind: {
Expand Down