From 2d75fd671708be2029ecfc2758cfb58ef3833256 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 16 Aug 2021 15:23:21 -0400 Subject: [PATCH] Upgrade to electron 12 and Node 14 - Compute cluster ID for tests - Switch to temporary appData path while testing Signed-off-by: Sebastian Malton --- integration/__tests__/app.tests.ts | 14 +-- integration/__tests__/cluster-pages.tests.ts | 42 +++++---- .../__tests__/command-palette.tests.ts | 4 +- integration/helpers/utils.ts | 87 ++++++++++++------- package.json | 4 +- src/common/vars.ts | 3 + src/main/index.ts | 33 ++++--- .../+apps-helm-charts/helm-charts.tsx | 1 - .../components/+apps-releases/releases.tsx | 2 +- src/renderer/utils/createStorage.ts | 2 +- yarn.lock | 17 ++-- 11 files changed, 112 insertions(+), 97 deletions(-) diff --git a/integration/__tests__/app.tests.ts b/integration/__tests__/app.tests.ts index 40db6a7934..4f5979425c 100644 --- a/integration/__tests__/app.tests.ts +++ b/integration/__tests__/app.tests.ts @@ -27,8 +27,6 @@ */ import * as utils from "../helpers/utils"; -jest.setTimeout(20_000); - describe("preferences page tests", () => { it('shows "preferences" and can navigate through the tabs', async () => { const { window, cleanup } = await utils.start(); @@ -59,15 +57,9 @@ describe("preferences page tests", () => { } finally { await cleanup(); } - }); + }, 10*60*1000); it("ensures helm repos", async () => { - const repos = await utils.listHelmRepositories(); - - if (repos.length === 0) { - fail("Lens failed to add any repositories"); - } - const { window, cleanup } = await utils.start(); try { @@ -75,7 +67,7 @@ describe("preferences page tests", () => { await window.keyboard.press("Meta+,"); await window.click("[data-testid=kubernetes-tab]"); - await window.waitForSelector(`[data-testid=repository-name] >> text=${repos[0].name}`, { + await window.waitForSelector("[data-testid=repository-name]", { timeout: 100_000, }); await window.click("#HelmRepoSelect"); @@ -83,5 +75,5 @@ describe("preferences page tests", () => { } finally { await cleanup(); } - }, 120_000); + }, 10*60*1000); }); diff --git a/integration/__tests__/cluster-pages.tests.ts b/integration/__tests__/cluster-pages.tests.ts index 7257de67cb..71fcb2c931 100644 --- a/integration/__tests__/cluster-pages.tests.ts +++ b/integration/__tests__/cluster-pages.tests.ts @@ -30,8 +30,6 @@ import { minikubeReady } from "../helpers/minikube"; const TEST_NAMESPACE = "integration-tests"; -jest.setTimeout(30_000); - function getSidebarSelectors(itemId: string) { const root = `.SidebarItem[data-test-id="${itemId}"]`; @@ -252,7 +250,7 @@ const commonPageTests: CommonPageTest[] = [{ name: "Releases", href: "apps/releases", expectedSelector: "h5.title", - expectedText: "Helm Releases" + expectedText: "Releases" }] }, { @@ -344,7 +342,7 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { } finally { await cleanup(); } - }); + }, 10*60*1000); it("show logs and highlight the log search entries", async () => { const { window, cleanup } = await utils.start(); @@ -354,11 +352,11 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { const frame = await utils.lauchMinikubeClusterFromCatalog(window); - if ((await frame.innerText(`a[href^="/workloads"] .Icon .icon`)) === "keyboard_arrow_down") { - await frame.click(`a[href^="/workloads"]`); + if ((await frame.innerText(`a[href="/workloads"] .expand-icon`)) === "keyboard_arrow_down") { + await frame.click(`a[href="/workloads"]`); } - await frame.click(`a[href^="/pods"]`); + await frame.click(`a[href="/pods"]`); const namespacesSelector = await frame.waitForSelector(".NamespaceSelect"); @@ -395,7 +393,7 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { } finally { await cleanup(); } - }); + }, 10*60*1000); it("should show the default namespaces", async () => { const { window, cleanup } = await utils.start(); @@ -411,7 +409,7 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { } finally { await cleanup(); } - }); + }, 10*60*1000); it(`should create the ${TEST_NAMESPACE} and a pod in the namespace`, async () => { const { window, cleanup } = await utils.start(); @@ -430,9 +428,9 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { await namespaceNameInput.type(TEST_NAMESPACE); await namespaceNameInput.press("Enter"); - await frame.waitForSelector(`div.TableCell >> text='${TEST_NAMESPACE}'`); + await frame.waitForSelector(`div.TableCell >> text=${TEST_NAMESPACE}`); - if ((await frame.innerText(`a[href^="/workloads"] .Icon .icon`)) === "keyboard_arrow_down") { + if ((await frame.innerText(`a[href^="/workloads"] .expand-icon`)) === "keyboard_arrow_down") { await frame.click(`a[href^="/workloads"]`); } @@ -446,14 +444,24 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { await namespacesSelector.click(); await frame.click(".Icon.new-dock-tab"); - await frame.waitForTimeout(500); // TODO: fix menus to be less flaky - const createResourceTabButtom = await frame.waitForSelector("li.MenuItem.create-resource-tab"); - await frame.waitForTimeout(500); // TODO: fix menus to be less flaky - await createResourceTabButtom.click(); + try { + await frame.click("li.MenuItem.create-resource-tab", { + // NOTE: the following shouldn't be required, but is because without it a TypeError is thrown + // see: https://github.com/microsoft/playwright/issues/8229 + position: { + y: 0, + x: 0, + } + }); + } catch (error) { + console.log(error); + await frame.waitForTimeout(100_000); + } - const inputField = await frame.waitForSelector(".CreateResource div.ace_content"); + const inputField = await frame.waitForSelector(".CreateResource div.react-monaco-editor-container"); + await inputField.click(); await inputField.type("apiVersion: v1", { delay: 10 }); await inputField.press("Enter", { delay: 10 }); await inputField.type("kind: Pod", { delay: 10 }); @@ -480,5 +488,5 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { } finally { await cleanup(); } - }); + }, 10*60*1000); }); diff --git a/integration/__tests__/command-palette.tests.ts b/integration/__tests__/command-palette.tests.ts index 5e99918f34..c895b7cf70 100644 --- a/integration/__tests__/command-palette.tests.ts +++ b/integration/__tests__/command-palette.tests.ts @@ -21,8 +21,6 @@ import * as utils from "../helpers/utils"; -jest.setTimeout(20_000); - describe("Lens command palette", () => { describe("menu", () => { it("opens command dialog from keyboard shortcut", async () => { @@ -35,6 +33,6 @@ describe("Lens command palette", () => { } finally { await cleanup(); } - }); + }, 10*60*1000); }); }); diff --git a/integration/helpers/utils.ts b/integration/helpers/utils.ts index e61d628bc2..f1876dc474 100644 --- a/integration/helpers/utils.ts +++ b/integration/helpers/utils.ts @@ -18,9 +18,12 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import * as util from "util"; -import { exec } from "child_process"; -import { Frame, Page, _electron as electron } from "playwright"; +import { createHash } from "crypto"; +import { mkdirp, remove } from "fs-extra"; +import * as os from "os"; +import * as path from "path"; +import * as uuid from "uuid"; +import { ElectronApplication, Frame, Page, _electron as electron } from "playwright"; export const AppPaths: Partial> = { "win32": "./dist/win-unpacked/OpenLens.exe", @@ -36,52 +39,74 @@ export function describeIf(condition: boolean) { return condition ? describe : describe.skip; } -export const promiseExec = util.promisify(exec); +async function getMainWindow(app: ElectronApplication, timeout = 50_000): Promise { + const deadline = Date.now() + timeout; -type HelmRepository = { - name: string; - url: string; -}; - -export async function listHelmRepositories(): Promise{ - for (let i = 0; i < 10; i += 1) { - try { - const { stdout } = await promiseExec("helm repo list -o json"); - - return JSON.parse(stdout); - } catch { - await new Promise(r => setTimeout(r, 2000)); // if no repositories, wait for Lens adding bitnami repository + for (; Date.now() < deadline;) { + for (const page of app.windows()) { + if (page.url().startsWith("http://localhost")) { + return page; + } } + + await new Promise(resolve => setTimeout(resolve, 2_000)); } - return []; + throw new Error(`Lens did not open the main window within ${timeout}ms`); +} + +async function closeApp(app: ElectronApplication, timeout = 30_000) { + // TODO: change once timeouts for the native function are available + await Promise.race([ + app.evaluateHandle(({ app }) => app.quit()), + new Promise((r, reject) => setTimeout(() => reject(`Lens failed to quit within ${timeout}ms`), timeout)), + ]); } export async function start() { + const CICD = path.join(os.tmpdir(), "lens-integration-testing", uuid.v4()); + + // Make sure that the directory is clear + await remove(CICD); + await mkdirp(CICD); + const app = await electron.launch({ args: ["--integration-testing"], // this argument turns off the blocking of quit executablePath: AppPaths[process.platform], bypassCSP: true, - }); + env: { CICD }, + timeout: 100_000, + } as Parameters[0]); - const window = await app.waitForEvent("window", { - predicate: async (page) => page.url().startsWith("http://localhost"), - }); + try { + const window = await getMainWindow(app); - return { - app, - window, - cleanup: async () => { - await window.close(); - await app.close(); - }, - }; + return { + app, + window, + cleanup: async () => { + try { + await window.close(); + } catch {} + await closeApp(app); + await remove(CICD); + }, + }; + } catch (error) { + await closeApp(app); + await remove(CICD); + throw error; + } } export async function clickWelcomeButton(window: Page) { await window.click("#hotbarIcon-catalog-entity .Icon"); } +function minikubeEntityId() { + return createHash("md5").update(`${path.join(os.homedir(), ".kube", "config")}:minikube`).digest("hex"); +} + /** * From the catalog, click the minikube entity and wait for it to connect, returning its frame */ @@ -91,7 +116,7 @@ export async function lauchMinikubeClusterFromCatalog(window: Page): Promise> text='KubernetesCluster: minikube'"); await window.click("div.EntityIcon div.HotbarIcon div div.MuiAvatar-root"); - const minikubeFrame = await window.waitForSelector("#cluster-frame-484e864bad9b84ce5d6b4fff704cc0e4"); + const minikubeFrame = await window.waitForSelector(`#cluster-frame-${minikubeEntityId()}`); const frame = await minikubeFrame.contentFrame(); diff --git a/package.json b/package.json index 1d085ab882..55a2c36891 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "build:linux": "yarn run compile && electron-builder --linux --dir", "build:mac": "yarn run compile && electron-builder --mac --dir", "build:win": "yarn run compile && electron-builder --win --dir", - "integration": "jest --runInBand integration", + "integration": "jest --runInBand --detectOpenHandles --forceExit integration", "dist": "yarn run compile && electron-builder --publish onTag", "dist:win": "yarn run compile && electron-builder --publish onTag --x64 --ia32", "dist:dir": "yarn run dist --dir -c.compression=store -c.mac.identity=null", @@ -353,7 +353,7 @@ "node-loader": "^1.0.3", "node-sass": "^4.14.1", "nodemon": "^2.0.12", - "playwright": "^1.13.1", + "playwright": "^1.14.0", "postcss": "^8.3.6", "postcss-loader": "4.0.3", "postinstall-postinstall": "^2.1.0", diff --git a/src/common/vars.ts b/src/common/vars.ts index dec81f313e..6462be4db5 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -35,6 +35,9 @@ export const isTestEnv = !!process.env.JEST_WORKER_ID; export const isDevelopment = !isTestEnv && !isProduction; export const isPublishConfigured = Object.keys(packageInfo.build).includes("publish"); +export const integrationTestingArg = "--integration-testing"; +export const isIntegrationTesting = process.argv.includes(integrationTestingArg); + export const productName = packageInfo.productName; export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`; export const publicPath = "/build/" as string; diff --git a/src/main/index.ts b/src/main/index.ts index 151b873ded..ce3cafd926 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -27,8 +27,7 @@ import * as Mobx from "mobx"; import * as LensExtensionsCommonApi from "../extensions/common-api"; import * as LensExtensionsMainApi from "../extensions/main-api"; import { app, autoUpdater, dialog, powerMonitor } from "electron"; -import { appName, isMac, productName } from "../common/vars"; -import path from "path"; +import { appName, isIntegrationTesting, isMac, productName } from "../common/vars"; import { LensProxy } from "./lens-proxy"; import { WindowManager } from "./window-manager"; import { ClusterManager } from "./cluster-manager"; @@ -64,13 +63,13 @@ import { ensureDir } from "fs-extra"; import { Router } from "./router"; import { initMenu } from "./menu"; import { initTray } from "./tray"; +import * as path from "path"; import { kubeApiRequest, shellApiRequest } from "./proxy-functions"; +const onCloseCleanup = disposer(); +const onQuitCleanup = disposer(); + SentryInit(); - -const workingDir = path.join(app.getPath("appData"), appName); -const cleanup = disposer(); - app.setName(appName); logger.info(`📟 Setting ${productName} as protocol client for lens://`); @@ -81,8 +80,9 @@ if (app.setAsDefaultProtocolClient("lens")) { logger.info("📟 Protocol client register failed ❗"); } -if (!process.env.CICD) { - app.setPath("userData", workingDir); +if (process.env.CICD) { + app.setPath("appData", process.env.CICD); + app.setPath("userData", path.join(process.env.CICD, appName)); } if (process.env.LENS_DISABLE_GPU) { @@ -123,7 +123,7 @@ app.on("second-instance", (event, argv) => { }); app.on("ready", async () => { - logger.info(`🚀 Starting ${productName} from "${workingDir}"`); + logger.info(`🚀 Starting ${productName} from "${app.getPath("exe")}"`); logger.info("🐚 Syncing shell environment"); await shellSync(); @@ -211,7 +211,7 @@ app.on("ready", async () => { logger.info("🖥️ Starting WindowManager"); const windowManager = WindowManager.createInstance(); - cleanup.push( + onQuitCleanup.push( initMenu(windowManager), initTray(windowManager), ); @@ -223,7 +223,7 @@ app.on("ready", async () => { } ipcMainOn(IpcRendererNavigationEvents.LOADED, async () => { - cleanup.push(pushCatalogToRenderer(catalogEntityRegistry)); + onCloseCleanup.push(pushCatalogToRenderer(catalogEntityRegistry)); await ensureDir(storedKubeConfigFolder()); KubeconfigSyncManager.getInstance().startSync(); startUpdateChecking(); @@ -271,11 +271,7 @@ app.on("activate", (event, hasVisibleWindows) => { /** * This variable should is used so that `autoUpdater.installAndQuit()` works */ -let blockQuit = true; - -if (process.argv.includes("--integration-testing")) { - blockQuit = false; -} +let blockQuit = !isIntegrationTesting; autoUpdater.on("before-quit-for-update", () => blockQuit = false); @@ -288,7 +284,7 @@ app.on("will-quit", (event) => { appEventBus.emit({ name: "app", action: "close" }); ClusterManager.getInstance(false)?.stop(); // close cluster connections KubeconfigSyncManager.getInstance(false)?.stopSync(); - cleanup(); + onCloseCleanup(); if (lprm) { // This is set to false here so that LPRM can wait to send future lens:// @@ -304,7 +300,8 @@ app.on("will-quit", (event) => { return; // skip exit to make tray work, to quit go to app's global menu or tray's menu } - LensProtocolRouterMain.getInstance(false)?.cleanup(); + lprm?.cleanup(); + onQuitCleanup(); }); app.on("open-url", (event, rawUrl) => { diff --git a/src/renderer/components/+apps-helm-charts/helm-charts.tsx b/src/renderer/components/+apps-helm-charts/helm-charts.tsx index 0cf58f7fb4..8a47654adc 100644 --- a/src/renderer/components/+apps-helm-charts/helm-charts.tsx +++ b/src/renderer/components/+apps-helm-charts/helm-charts.tsx @@ -86,7 +86,6 @@ export class HelmCharts extends Component { [columnId.name]: chart => chart.getName(), [columnId.repo]: chart => chart.getRepository(), }} - renderHeaderTitle="Helm Charts" searchFilters={[ chart => chart.getName(), chart => chart.getVersion(), diff --git a/src/renderer/components/+apps-releases/releases.tsx b/src/renderer/components/+apps-releases/releases.tsx index c35a889c6e..d43174cc10 100644 --- a/src/renderer/components/+apps-releases/releases.tsx +++ b/src/renderer/components/+apps-releases/releases.tsx @@ -138,7 +138,7 @@ export class HelmReleases extends Component { }, ...headerPlaceholders, })} - renderHeaderTitle="Helm Releases" + renderHeaderTitle="Releases" renderTableHeader={[ { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, diff --git a/src/renderer/utils/createStorage.ts b/src/renderer/utils/createStorage.ts index e5c168cf0f..df68e17b65 100755 --- a/src/renderer/utils/createStorage.ts +++ b/src/renderer/utils/createStorage.ts @@ -50,7 +50,7 @@ export function createAppStorage(key: string, defaultValue: T, clusterId?: st const fileName = `${clusterId ?? "app"}.json`; const filePath = path.resolve(folder, fileName); - if (!storage.initialized && !process.argv.includes("--integration-tests")) { + if (!storage.initialized) { storage.initialized = true; // read previously saved state (if any) diff --git a/yarn.lock b/yarn.lock index 673c1fce4c..9268ab82da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10761,7 +10761,7 @@ os-locale@^2.0.0: lcid "^1.0.0" mem "^1.1.0" -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= @@ -11200,10 +11200,10 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -playwright@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.13.1.tgz#53c49244ffa7fc2d5d8bf7ce87739393206eda5e" - integrity sha512-pGbILcUV1+Yx0fcRJZBEIsqTDjQSwGYkJ4tVUQUtuAyMuHg5B++Nasl+pIeIJRMqKSWTyvZfPxJuPIGsLJgiFg== +playwright@^1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.14.0.tgz#18301b11f5278a446d36b5cf96f67db36ce2cd20" + integrity sha512-aR5oZ1iVsjQkGfYCjgYAmyMAVu0MQ0i8MgdnfdqDu9EVLfbnpuuFmTv/Rb7/Yjno1kOrDUP9+RyNC+zfG3wozA== dependencies: commander "^6.1.0" debug "^4.1.1" @@ -13899,13 +13899,6 @@ tmp-promise@^3.0.2: dependencies: tmp "^0.2.0" -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - tmp@^0.2.0, tmp@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"