Repository: microsoft/vscode-edge-devtools
Pull request #147 (2020-05-22 00:34:10)

Converting from browserPath to browserFlavor
This PR removes the _browserPath setting_ and creates a _browser flavor combo box_ for a similar purpose.

![image](https://user-images.githubusercontent.com/43078681/82618496-1a75b280-9b88-11ea-875c-0f86ce1392e3.png)

**Current Behavior:**
The user can specify (via a input box) the path to a browser version, when no path is specified the extension searches for stable > beta > dev > canary (in that order) until an existing version is found on disk.

**New Behavior**
When default is selected, extension will keep current behavior, otherwise it will use the version specified by the user. 

Test are also modified to use a specific flavor instead of a custom path.


mliao95: Just did a quick first pass and it works well!  One NIT would be to just capitalize the options.  Will take a closer look tomorrow.
vidorteg: > Just did a quick first pass and it works well! One NIT would be to just capitalize the options. Will take a closer look tomorrow.

done :)

Patch:
diff --git a/package.json b/package.json
index 1c67dda..f276b20 100644
--- a/package.json
+++ b/package.json
@@ -91,11 +91,6 @@
             "title": "Microsoft Edge (Chromium) Tools",
             "type": "object",
             "properties": {
-                "vscode-edge-devtools.browserPath": {
-                    "type": "string",
-                    "default": "",
-                    "description": "The path to the browser to use, rather than searching for the default"
-                },
                 "vscode-edge-devtools.hostname": {
                     "type": "string",
                     "default": "localhost",
@@ -166,6 +161,23 @@
                     "type": "number",
                     "description": "The number of milliseconds that the Microsoft Edge Tools will keep trying to attach the browser before timing out",
                     "default": 10000
+                },
+                "vscode-edge-devtools.browserFlavor": {
+                    "type": "string",
+                    "enum": [
+                        "Default",
+                        "Stable",
+                        "Beta",
+                        "Dev",
+                        "Canary"
+                    ],
+                    "enumDescriptions": [
+                        "Microsoft Edge (Chromium) Tools for VSCode will try to open the Microsoft Edge flavors in the following order: Stable, Beta, Dev and Canary",
+                        "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Stable version",
+                        "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Beta version",
+                        "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Dev version",
+                        "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Canary version"
+                    ]
                 }
             }
         },
@@ -210,11 +222,6 @@
                                 "description": "File path to launch",
                                 "default": "${workspaceFolder}/index.html"
                             },
-                            "browserPath": {
-                                "type": "string",
-                                "description": "Absolute path to the browser instance to launch",
-                                "default": ""
-                            },
                             "hostname": {
                                 "type": "string",
                                 "default": "localhost",
@@ -265,6 +272,23 @@
                                 "type": "boolean",
                                 "description": "Use JavaScript source maps (if they exist)",
                                 "default": true
+                            },
+                            "vscode-edge-devtools.browserFlavor": {
+                                "type": "string",
+                                "enum": [
+                                    "Default",
+                                    "Stable",
+                                    "Beta",
+                                    "Dev",
+                                    "Canary"
+                                ],
+                                "enumDescriptions": [
+                                    "Microsoft Edge (Chromium) Tools for VSCode will try to open the Microsoft Edge flavors in the following order: Stable, Beta, Dev and Canary",
+                                    "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Stable version",
+                                    "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Beta version",
+                                    "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Dev version",
+                                    "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Canary version"
+                                ]
                             }
                         }
                     },
@@ -280,11 +304,6 @@
                                 "description": "File path to launch.",
                                 "default": "${workspaceFolder}/index.html"
                             },
-                            "browserPath": {
-                                "type": "string",
-                                "description": "Absolute path to the browser instance to launch.",
-                                "default": ""
-                            },
                             "hostname": {
                                 "type": "string",
                                 "default": "localhost",
@@ -335,6 +354,23 @@
                                 "type": "boolean",
                                 "description": "Use JavaScript source maps (if they exist)",
                                 "default": true
+                            },
+                            "vscode-edge-devtools.browserFlavor": {
+                                "type": "string",
+                                "enum": [
+                                    "Default",
+                                    "Stable",
+                                    "Beta",
+                                    "Dev",
+                                    "Canary"
+                                ],
+                                "enumDescriptions": [
+                                    "Microsoft Edge (Chromium) Tools for VSCode will try to open the Microsoft Edge flavors in the following order: Stable, Beta, Dev and Canary",
+                                    "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Stable version",
+                                    "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Beta version",
+                                    "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Dev version",
+                                    "Microsoft Edge (Chromium) Tools for VSCode will use Microsoft Edge Canary version"
+                                ]
                             }
                         }
                     }
diff --git a/src/extension.ts b/src/extension.ts
index 9f0409c..a501032 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -188,8 +188,9 @@ export async function launch(context: vscode.ExtensionContext, launchUrl?: strin
             telemetryReporter.sendTelemetryEvent("command/launch/error/browser_not_found", telemetryProps);
             vscode.window.showErrorMessage(
                 "Microsoft Edge could not be found. " +
-                "Ensure you have installed Microsoft Edge, " +
-                "or try specifying a custom path via the 'browserPath' setting.");
+                "Ensure you have installed Microsoft Edge " +
+                "and that you have selected 'default' or the appropriate version of Microsoft Edge " +
+                "in the extension settings panel.");
             return;
         } else {
             // Here we grab the last part of the path (using either forward or back slashes to account for mac/win),
diff --git a/src/utils.ts b/src/utils.ts
index 1b8452b..db8c3c8 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -14,6 +14,16 @@ import TelemetryReporter from "vscode-extension-telemetry";
 import packageJson from "../package.json";
 import DebugTelemetryReporter from "./debugTelemetryReporter";
 
+export type BrowserFlavor = "Default" | "Stable" | "Beta" | "Dev" | "Canary";
+
+interface IBrowserPath {
+    windows: {
+        primary: string;
+        secondary: string;
+    };
+    osx: string;
+}
+
 export interface IDevToolsSettings {
     hostname: string;
     port: number;
@@ -26,7 +36,7 @@ export interface IDevToolsSettings {
 export interface IUserConfig {
     url: string;
     urlFilter: string;
-    browserPath: string;
+    browserFlavor: BrowserFlavor;
     hostname: string;
     port: number;
     useHttps: boolean;
@@ -79,22 +89,7 @@ export const SETTINGS_DEFAULT_ATTACH_TIMEOUT: number = 10000;
 export const SETTINGS_DEFAULT_ATTACH_INTERVAL: number = 200;
 
 const WIN_APP_DATA = process.env.LOCALAPPDATA || "/";
-const WIN_MSEDGE_PATHS = [
-    "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",        // Stable
-    path.join(WIN_APP_DATA, "Microsoft\\Edge\\Application\\msedge.exe"),        // Stable localappdata
-    "C:\\Program Files (x86)\\Microsoft\\Edge Beta\\Application\\msedge.exe",   // Beta
-    path.join(WIN_APP_DATA, "Microsoft\\Edge Beta\\Application\\msedge.exe"),   // Beta localappdata
-    "C:\\Program Files (x86)\\Microsoft\\Edge Dev\\Application\\msedge.exe",    // Dev
-    path.join(WIN_APP_DATA, "Microsoft\\Edge Dev\\Application\\msedge.exe"),    // Dev localappdata
-    "C:\\Program Files (x86)\\Microsoft\\Edge SxS\\Application\\msedge.exe",    // Canary
-    path.join(WIN_APP_DATA, "Microsoft\\Edge SxS\\Application\\msedge.exe"),    // Canary localappdata
-];
-const OSX_MSEDGE_PATHS = [
-    "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
-    "/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta",
-    "/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev",
-    "/Applications/Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary",
-];
+const msEdgeBrowserMapping: Map<BrowserFlavor, IBrowserPath> = new Map();
 
 export interface IRemoteTargetJson {
     [index: string]: string;
@@ -214,7 +209,7 @@ export function getRemoteEndpointSettings(config: Partial<IUserConfig> = {}): ID
     // We generate a temp directory if the user opted in explicitly with 'true' (which is the default),
     // Or if it is not defined and they are not using a custom browser path (such as electron).
     // This matches the behavior of the chrome and edge debug extensions.
-    const browserPath = config.browserPath || settings.get("browserPath") || "";
+    const browserPathSet = config.browserFlavor || "Default";
     let userDataDir: string | boolean | undefined;
     if (typeof config.userDataDir !== "undefined") {
         userDataDir = config.userDataDir;
@@ -225,7 +220,7 @@ export function getRemoteEndpointSettings(config: Partial<IUserConfig> = {}): ID
         }
     }
 
-    if (userDataDir === true || (typeof userDataDir === "undefined" && !browserPath)) {
+    if (userDataDir === true || (typeof userDataDir === "undefined" && browserPathSet === "Default")) {
         // Generate a temp directory
         userDataDir = path.join(os.tmpdir(), `vscode-edge-devtools-userdatadir_${port}`);
     } else if (!userDataDir) {
@@ -261,32 +256,23 @@ export function getPlatform(): Platform {
 }
 
 /**
- * Get the path to the first valid browser for the current session
- * The search order is: launchConfig > vscode setting > platform default
- * For each platform the order is: stable > beta > dev > canary
- * For windows we will try: program files > local app data
+ * Gets the browser path for the specified browser flavor.
  * @param config The settings specified by a launch config, if any
  */
-export async function getBrowserPath(config: Partial<IUserConfig> = {}) {
+export async function getBrowserPath(config: Partial<IUserConfig> = {}): Promise<string> {
     const settings = vscode.workspace.getConfiguration(SETTINGS_STORE_NAME);
-    const browserPath = config.browserPath || settings.get("browserPath") || "";
-
-    if (!browserPath) {
-        const platform = getPlatform();
-        const searchPaths = platform === "Windows" ? WIN_MSEDGE_PATHS :
-            platform === "OSX" ? OSX_MSEDGE_PATHS :
-                [];
-
-        // Find the first one that exists
-        for (const p of searchPaths) {
-            if (await fse.pathExists(p)) {
-                return p;
-            }
+    const flavor: BrowserFlavor | undefined = config.browserFlavor || settings.get("browserFlavor");
+
+    switch (getPlatform()) {
+        case "Windows": {
+           return await verifyFlavorPath(flavor, "Windows");
+        }
+        case "OSX": {
+            return await verifyFlavorPath(flavor, "OSX");
         }
     }
 
-    // Only return it if it exists
-    return (await fse.pathExists(browserPath) ? browserPath : "");
+    return "";
 }
 
 /**
@@ -451,3 +437,80 @@ export function applyPathMapping(
 
     return sourcePath;
 }
+
+/**
+ * Verifies and returns if the browser for the current session exists in the
+ * desired flavor and platform. Providing a "default" flavor will scan for the
+ * first browser available in the following order:
+ * stable > beta > dev > canary
+ * For windows it will try: program files > local app data
+ * @param flavor the desired browser flavor
+ * @param platform the desired platform
+ * @returns a promise with the path to the browser or an empty string if not found.
+ */
+async function verifyFlavorPath(flavor: BrowserFlavor | undefined, platform: Platform): Promise<string> {
+    let item = msEdgeBrowserMapping.get(flavor || "Default");
+    if (!item) {
+        // if no flavor is specified search for any path present.
+        for (item of msEdgeBrowserMapping.values()) {
+            const result = await findFlavorPath(item);
+            if (result) {
+                return result;
+            }
+        }
+    }
+
+    return await findFlavorPath(item);
+
+    // Verifies if the path existis in disk.
+    async function findFlavorPath(browserPath: IBrowserPath | undefined) {
+        if (!browserPath) {
+            return "";
+        }
+
+        if (await fse.pathExists(browserPath.windows.primary) &&
+            (platform === "Windows" || flavor === "Default")) {
+            return browserPath.windows.primary;
+        } else if (await fse.pathExists(browserPath.windows.secondary) &&
+            (platform === "Windows" || flavor === "Default")) {
+            return browserPath.windows.secondary;
+        } else if (await fse.pathExists(browserPath.osx) &&
+            (platform === "OSX" || flavor === "Default")) {
+            return browserPath.osx;
+        }
+
+        return "";
+    }
+}
+
+(function initialize() {
+    // insertion order matters.
+    msEdgeBrowserMapping.set("Stable", {
+        osx: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
+        windows: {
+            primary: "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
+            secondary: path.join(WIN_APP_DATA, "Microsoft\\Edge\\Application\\msedge.exe"),
+        },
+    });
+    msEdgeBrowserMapping.set("Beta", {
+        osx: "/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta",
+        windows: {
+            primary: "C:\\Program Files (x86)\\Microsoft\\Edge Beta\\Application\\msedge.exe",
+            secondary: path.join(WIN_APP_DATA, "Microsoft\\Edge Beta\\Application\\msedge.exe"),
+        },
+    });
+    msEdgeBrowserMapping.set("Dev", {
+        osx: "/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev",
+        windows: {
+            primary: "C:\\Program Files (x86)\\Microsoft\\Edge Dev\\Application\\msedge.exe",
+            secondary: path.join(WIN_APP_DATA, "Microsoft\\Edge Dev\\Application\\msedge.exe"),
+        },
+    });
+    msEdgeBrowserMapping.set("Canary", {
+        osx: "/Applications/Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary",
+        windows: {
+            primary: "C:\\Program Files (x86)\\Microsoft\\Edge SxS\\Application\\msedge.exe",
+            secondary: path.join(WIN_APP_DATA, "Microsoft\\Edge SxS\\Application\\msedge.exe"),
+        },
+    });
+})();


Test patch:
diff --git a/src/utils.test.ts b/src/utils.test.ts
index 595ec64..c2b8997 100644
--- a/src/utils.test.ts
+++ b/src/utils.test.ts
@@ -5,8 +5,9 @@
 // tslint:disable: variable-name
 
 import * as path from "path";
+
 import { createFakeExtensionContext, createFakeGet, createFakeVSCode, Mocked } from "./test/helpers";
-import { IRemoteTargetJson, IUserConfig } from "./utils";
+import { BrowserFlavor, IRemoteTargetJson, IUserConfig } from "./utils";
 
 jest.mock("vscode", () => null, { virtual: true });
 
@@ -305,6 +306,7 @@ describe("utils", () => {
 
         it("uses user config", async () => {
             const config = {
+                browserPath: "Default",
                 hostname: "someHost",
                 port: 9999,
                 url: "url",
@@ -373,7 +375,7 @@ describe("utils", () => {
             expect(result5.userDataDir).toEqual(expect.stringContaining("vscode-edge-devtools-userdatadir_"));
 
             // No folder if they use a browser path
-            const result6 = utils.getRemoteEndpointSettings({ browserPath: "someBrowser.exe" });
+            const result6 = utils.getRemoteEndpointSettings({ browserFlavor: "Stable" });
             expect(result6.userDataDir).toEqual("");
         });
     });
@@ -467,12 +469,13 @@ describe("utils", () => {
 
         it("returns the custom path or empty string", async () => {
             const config = {
-                browserPath: "someCustomPath.exe",
+                browserFlavor: "Default" as BrowserFlavor,
             };
+            os.platform.mockReturnValue("win32");
 
             // Ensure we get a valid path back
             fse.pathExists.mockImplementation(() => Promise.resolve(true));
-            expect(await utils.getBrowserPath(config)).toEqual(config.browserPath);
+            expect(await utils.getBrowserPath(config)).not.toEqual("");
 
             // Ensure we get "" on bad path
             fse.pathExists.mockImplementation(() => Promise.resolve(false));
@@ -480,12 +483,17 @@ describe("utils", () => {
         });
 
         it("returns the settings path", async () => {
-            const expectedPath = "someCustomPath.exe";
+            const expectedFlavor = "Default";
+            const expectedPath = "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe";
             const configMock = {
-                get: (name: string) => expectedPath,
+                get: (name: string) => expectedFlavor,
             };
             const vscodeMock = await jest.requireMock("vscode");
+            os.platform.mockReturnValue("win32");
             vscodeMock.workspace.getConfiguration.mockImplementationOnce(() => configMock);
+            fse.pathExists.mockImplementation(() => {
+                return Promise.resolve(true);
+            });
 
             expect(await utils.getBrowserPath()).toEqual(expectedPath);
         });
@@ -493,25 +501,47 @@ describe("utils", () => {
         it("searches for path", async () => {
             // Check Windows
             global.process.env.LOCALAPPDATA = "";
+            const vscodeMock = await jest.requireMock("vscode");
+            let matchingRegex = /.+Microsoft\\Edge\\Application\\msedge.exe/g;
             os.platform.mockReturnValue("win32");
-            fse.pathExists.mockImplementation(() => {
-                return Promise.resolve(fse.pathExists.mock.calls.length === 8);
+            vscodeMock.workspace.getConfiguration.mockImplementationOnce(() => {
+                return {
+                    get: (name: string) => "Default",
+                };
             });
-            expect(await utils.getBrowserPath()).toEqual(expect.stringMatching(/.+msedge.exe/g));
-
-            fse.pathExists.mockClear();
+            fse.pathExists.mockImplementation((customPath) => {
+                if (customPath && customPath.match(matchingRegex)) {
+                    return Promise.resolve(true);
+                }
+                return Promise.resolve(false);
+            });
+            expect(await utils.getBrowserPath()).toEqual(expect.stringMatching(matchingRegex));
 
             // Check OSX
+            matchingRegex = /.+Microsoft Edge Canary\.app.+/g;
             os.platform.mockReturnValue("darwin");
-            fse.pathExists.mockImplementation(() => {
-                return Promise.resolve(fse.pathExists.mock.calls.length === 4);
-            });
-            expect(await utils.getBrowserPath()).toEqual(expect.stringMatching(/.+Microsoft Edge Canary/g));
-
             fse.pathExists.mockClear();
+            fse.pathExists.mockImplementation((customPath) => {
+                if (customPath && customPath.match(matchingRegex)) {
+                    return Promise.resolve(true);
+                }
+                return Promise.resolve(false);
+            });
+            vscodeMock.workspace.getConfiguration.mockImplementationOnce(() => {
+                return {
+                    get: (name: string) => "Canary",
+                };
+            });
+            expect(await utils.getBrowserPath()).toEqual(expect.stringMatching(matchingRegex));
 
             // Check Linux
             os.platform.mockReturnValue("linux");
+            vscodeMock.workspace.getConfiguration.mockImplementationOnce(() => {
+                return {
+                    get: (name: string) => "Dev",
+                };
+            });
+            fse.pathExists.mockClear();
             fse.pathExists.mockImplementation(() => Promise.resolve(false));
             expect(await utils.getBrowserPath()).toEqual("");
         });