From 61831b3da76bd375788bc67a7d1dfa0733d0cd69 Mon Sep 17 00:00:00 2001 From: Priyanshu Agrawal Date: Tue, 14 Jul 2026 21:57:35 +0530 Subject: [PATCH] Use integrated browser for site preview Open Power Pages site previews in VS Code's integrated browser when the command is available, and keep the existing Edge Tools flow as the fallback for older builds. Copilot-Session: e313faed-11b2-4875-88ea-6cb213b7f097 --- .../handlers/PreviewSiteHandler.ts | 2 +- .../power-pages/preview-site/PreviewSite.ts | 46 +++++++++++++++---- .../handlers/PreviewSiteHandler.test.ts | 8 ++-- .../preview-site/PreviewSite.test.ts | 41 +++++++++++++++++ src/client/test/runTest.ts | 13 +++++- 5 files changed, 95 insertions(+), 15 deletions(-) diff --git a/src/client/power-pages/actions-hub/handlers/PreviewSiteHandler.ts b/src/client/power-pages/actions-hub/handlers/PreviewSiteHandler.ts index 24b910c5f..af9c990e0 100644 --- a/src/client/power-pages/actions-hub/handlers/PreviewSiteHandler.ts +++ b/src/client/power-pages/actions-hub/handlers/PreviewSiteHandler.ts @@ -13,7 +13,7 @@ export const previewSite = async (siteTreeItem: SiteTreeItem) => { try { await PreviewSite.clearCache(siteTreeItem.siteInfo.websiteUrl); - await PreviewSite.launchBrowserAndDevToolsWithinVsCode(siteTreeItem.siteInfo.websiteUrl, siteTreeItem.siteInfo.dataModelVersion, siteTreeItem.siteInfo.siteVisibility); + await PreviewSite.launchSitePreviewWithinVsCode(siteTreeItem.siteInfo.websiteUrl, siteTreeItem.siteInfo.dataModelVersion, siteTreeItem.siteInfo.siteVisibility); } catch (error) { traceError(Constants.EventNames.ACTIONS_HUB_PREVIEW_SITE_FAILED, error as Error, { methodName: previewSite.name }); } diff --git a/src/client/power-pages/preview-site/PreviewSite.ts b/src/client/power-pages/preview-site/PreviewSite.ts index d098a6ca4..ad7694fcd 100644 --- a/src/client/power-pages/preview-site/PreviewSite.ts +++ b/src/client/power-pages/preview-site/PreviewSite.ts @@ -26,6 +26,8 @@ import { SiteVisibility } from '../actions-hub/models/SiteVisibility'; import { uploadSite } from '../actions-hub/handlers/UploadSiteHandler'; export const SITE_PREVIEW_COMMAND_ID = "microsoft.powerplatform.pages.preview-site"; +const INTEGRATED_BROWSER_COMMAND_ID = 'workbench.action.browser.open'; +const EDGE_DEVTOOLS_COMMAND_ID = 'vscode-edge-devtools.launch'; export class PreviewSite { private static _websiteDetails: IWebsiteDetails | undefined = undefined; @@ -126,15 +128,13 @@ export class PreviewSite { } } - public static async launchBrowserAndDevToolsWithinVsCode(webSitePreviewURL: string | undefined, dataModelVersion: 1 | 2, siteVisibility: SiteVisibility | undefined): Promise { + public static async launchSitePreviewWithinVsCode(webSitePreviewURL: string | undefined, dataModelVersion: 1 | 2, siteVisibility: SiteVisibility | undefined): Promise { if (!webSitePreviewURL || webSitePreviewURL === "" || !siteVisibility) { return; } - const edgeToolsExtension = vscode.extensions.getExtension(EDGE_TOOLS_EXTENSION_ID); - - if (!edgeToolsExtension) { - await PreviewSite.promptInstallEdgeTools(); + const previewBrowser = await PreviewSite.getSitePreviewBrowser(); + if (!previewBrowser) { return; } @@ -142,8 +142,7 @@ export class PreviewSite { Messages.OPENING_SITE_PREVIEW, false, async () => { - PreviewSite.closeExistingPreview(); - await vscode.commands.executeCommand('vscode-edge-devtools.launch', { launchUrl: webSitePreviewURL }); + await PreviewSite.openSitePreviewBrowser(webSitePreviewURL, previewBrowser); } ); @@ -153,6 +152,37 @@ export class PreviewSite { } } + private static async getSitePreviewBrowser(): Promise<'integratedBrowser' | 'edgeTools' | undefined> { + const commands = await vscode.commands.getCommands(true); + + // VS Code's integrated browser is available through a workbench command in + // newer builds. Older builds fall back to the existing Edge Tools path because + // Simple Browser does not fully render Power Pages sites. + // See https://github.com/microsoft/vscode/blob/main/extensions/simple-browser/src/extension.ts + if (commands.includes(INTEGRATED_BROWSER_COMMAND_ID)) { + return 'integratedBrowser'; + } + + const edgeToolsExtension = vscode.extensions.getExtension(EDGE_TOOLS_EXTENSION_ID); + + if (!edgeToolsExtension) { + await PreviewSite.promptInstallEdgeTools(); + return undefined; + } + + return 'edgeTools'; + } + + private static async openSitePreviewBrowser(webSitePreviewURL: string, previewBrowser: 'integratedBrowser' | 'edgeTools'): Promise { + if (previewBrowser === 'integratedBrowser') { + await vscode.commands.executeCommand(INTEGRATED_BROWSER_COMMAND_ID, webSitePreviewURL); + return; + } + + PreviewSite.closeExistingPreview(); + await vscode.commands.executeCommand(EDGE_DEVTOOLS_COMMAND_ID, { launchUrl: webSitePreviewURL }); + } + private static async showUploadWarning(websitePath: string, dataModelVersion: 1 | 2, siteVisibility: SiteVisibility) { const pendingChangesResult = await PreviewSite._pacTerminal.getWrapper().pendingChanges(websitePath, dataModelVersion); @@ -219,7 +249,7 @@ export class PreviewSite { await PreviewSite.clearCache(PreviewSite._websiteDetails.websiteUrl); - await PreviewSite.launchBrowserAndDevToolsWithinVsCode( + await PreviewSite.launchSitePreviewWithinVsCode( PreviewSite._websiteDetails.websiteUrl, PreviewSite._websiteDetails.dataModel === WebsiteDataModel.Standard ? 1 : 2, PreviewSite._websiteDetails.siteVisibility diff --git a/src/client/test/integration/power-pages/actions-hub/handlers/PreviewSiteHandler.test.ts b/src/client/test/integration/power-pages/actions-hub/handlers/PreviewSiteHandler.test.ts index fa94100ce..768909383 100644 --- a/src/client/test/integration/power-pages/actions-hub/handlers/PreviewSiteHandler.test.ts +++ b/src/client/test/integration/power-pages/actions-hub/handlers/PreviewSiteHandler.test.ts @@ -32,12 +32,12 @@ describe('PreviewSiteHandler', () => { describe('previewSite', () => { let mockPreviewSiteClearCache: sinon.SinonStub; - let mockLaunchBrowserAndDevTools: sinon.SinonStub; + let mockLaunchSitePreview: sinon.SinonStub; let mockSiteInfo: IWebsiteInfo; beforeEach(() => { mockPreviewSiteClearCache = sandbox.stub(PreviewSite, 'clearCache'); - mockLaunchBrowserAndDevTools = sandbox.stub(PreviewSite, 'launchBrowserAndDevToolsWithinVsCode'); + mockLaunchSitePreview = sandbox.stub(PreviewSite, 'launchSitePreviewWithinVsCode'); mockSiteInfo = { name: "Test Site", websiteId: "test-id", @@ -53,13 +53,13 @@ describe('PreviewSiteHandler', () => { }; }); - it('should clear cache and launch browser with dev tools', async () => { + it('should clear cache and launch site preview', async () => { const siteTreeItem = new SiteTreeItem(mockSiteInfo); await previewSite(siteTreeItem); expect(mockPreviewSiteClearCache.calledOnceWith('https://test-site.com')).to.be.true; - expect(mockLaunchBrowserAndDevTools.calledOnceWith('https://test-site.com', 1)).to.be.true; + expect(mockLaunchSitePreview.calledOnceWith('https://test-site.com', 1)).to.be.true; }); }); }); diff --git a/src/client/test/integration/power-pages/preview-site/PreviewSite.test.ts b/src/client/test/integration/power-pages/preview-site/PreviewSite.test.ts index b6ace7403..44797dda3 100644 --- a/src/client/test/integration/power-pages/preview-site/PreviewSite.test.ts +++ b/src/client/test/integration/power-pages/preview-site/PreviewSite.test.ts @@ -13,6 +13,7 @@ import { Events } from '../../../../power-pages/preview-site/Constants'; import ArtemisContext from '../../../../ArtemisContext'; import PacContext from '../../../../pac/PacContext'; import * as sinon from 'sinon'; +import { SiteVisibility } from '../../../../power-pages/actions-hub/models/SiteVisibility'; describe('PreviewSite', () => { let sandbox: sinon.SinonSandbox; @@ -182,4 +183,44 @@ describe('PreviewSite', () => { expect(getWebSiteUrlStub.calledOnceWith(mockWorkspaceFolders)).to.be.true; }); }); + + describe('launchSitePreviewWithinVsCode', () => { + let executeCommandStub: sinon.SinonStub; + + beforeEach(() => { + executeCommandStub = sandbox.stub(vscode.commands, 'executeCommand'); + sandbox.stub(vscode.window, 'withProgress').callsFake(async (_options, task) => { + return await task({ report: sandbox.stub() }, {} as vscode.CancellationToken); + }); + sandbox.stub(PreviewSite as unknown as { showUploadWarning: () => Promise }, 'showUploadWarning').resolves(); + }); + + it('should open the integrated browser when the VS Code command is available', async () => { + sandbox.stub(vscode.commands, 'getCommands').resolves(['workbench.action.browser.open']); + + await PreviewSite.launchSitePreviewWithinVsCode('https://test-site.com', 1, SiteVisibility.Public); + + expect(executeCommandStub.calledOnceWith('workbench.action.browser.open', 'https://test-site.com')).to.be.true; + }); + + it('should fall back to Edge Tools when the integrated browser command is not available', async () => { + sandbox.stub(vscode.commands, 'getCommands').resolves(['vscode-edge-devtools.launch']); + sandbox.stub(vscode.extensions, 'getExtension').withArgs('ms-edgedevtools.vscode-edge-devtools').returns({} as vscode.Extension); + + await PreviewSite.launchSitePreviewWithinVsCode('https://test-site.com', 1, SiteVisibility.Public); + + expect(executeCommandStub.calledOnceWith('vscode-edge-devtools.launch', { launchUrl: 'https://test-site.com' })).to.be.true; + }); + + it('should prompt to install Edge Tools when no preview browser is available', async () => { + sandbox.stub(vscode.commands, 'getCommands').resolves([]); + sandbox.stub(vscode.extensions, 'getExtension').withArgs('ms-edgedevtools.vscode-edge-devtools').returns(undefined); + const showWarningMessageStub = sandbox.stub(vscode.window, 'showWarningMessage'); + + await PreviewSite.launchSitePreviewWithinVsCode('https://test-site.com', 1, SiteVisibility.Public); + + expect(showWarningMessageStub.calledOnce).to.be.true; + expect(executeCommandStub.called).to.be.false; + }); + }); }); diff --git a/src/client/test/runTest.ts b/src/client/test/runTest.ts index 9922318cd..5875aab7c 100644 --- a/src/client/test/runTest.ts +++ b/src/client/test/runTest.ts @@ -4,10 +4,17 @@ */ import * as path from "path"; +import * as fs from "fs"; +import * as os from "os"; import { runTests } from "@vscode/test-electron"; async function main() { + // VS Code creates IPC sockets under the user-data directory. Keeping that + // directory in the system temp path avoids macOS Unix socket path limits when + // tests run from long worktree paths. + const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "pp-vscode-client-")); + try { // The folder containing the Extension Manifest package.json // Passed to `--extensionDevelopmentPath` @@ -24,11 +31,13 @@ async function main() { // version: 'insiders', extensionDevelopmentPath, extensionTestsPath, - launchArgs: ['--no-sandbox', '--disable-gpu'] + launchArgs: ['--no-sandbox', '--disable-gpu', `--user-data-dir=${userDataDir}`] }); } catch (err) { console.error("Failed to run tests"); - process.exit(1); + throw err; + } finally { + fs.rmSync(userDataDir, { recursive: true, force: true }); } }