From d0e69ab84b7488e7c3b85a75b5e1a4c864f8196c Mon Sep 17 00:00:00 2001 From: Gordon Smith Date: Wed, 4 Dec 2024 12:07:55 +0000 Subject: [PATCH] HPCC-33066: Add basic ECL Watch UI tests Signed-off-by: Gordon Smith --- .github/workflows/build-test-eclwatch.yml | 8 +- esp/src/.gitignore | 4 + esp/src/lws.config.js | 4 +- esp/src/package-lock.json | 116 ++++++++++++++++++++-- esp/src/package.json | 9 +- esp/src/playwright.config.ts | 47 +++++++++ esp/src/tests/eclwatch-v5.spec.ts | 34 +++++++ esp/src/tests/eclwatch-v9.spec.ts | 44 ++++++++ 8 files changed, 254 insertions(+), 12 deletions(-) create mode 100644 esp/src/playwright.config.ts create mode 100644 esp/src/tests/eclwatch-v5.spec.ts create mode 100644 esp/src/tests/eclwatch-v9.spec.ts diff --git a/.github/workflows/build-test-eclwatch.yml b/.github/workflows/build-test-eclwatch.yml index 7a9c561af5f..ff4d53c36c1 100644 --- a/.github/workflows/build-test-eclwatch.yml +++ b/.github/workflows/build-test-eclwatch.yml @@ -25,7 +25,7 @@ jobs: build: strategy: matrix: - node: ["20", "18", "16"] + node: ["22", "20", "18"] fail-fast: false name: "Check eclwatch and npm" needs: pre_job @@ -48,6 +48,12 @@ jobs: - name: Install Dependencies working-directory: ./esp/src run: npm ci + - name: Lint + working-directory: ./esp/src + run: npm run lint + - name: Install Playwright browsers + working-directory: ./esp/src + run: npx playwright install --with-deps - name: Build working-directory: ./esp/src run: npm run build diff --git a/esp/src/.gitignore b/esp/src/.gitignore index a2270e87528..09c76804ba1 100644 --- a/esp/src/.gitignore +++ b/esp/src/.gitignore @@ -1,7 +1,11 @@ +blob-report/ build/ hpcc-js/ lib/ node_modules/ +playwright/.cache/ +playwright-report/ +test-results/ types/ .vscode/* !.vscode/tasks.json diff --git a/esp/src/lws.config.js b/esp/src/lws.config.js index 39dfa94dcb8..00826fdf310 100644 --- a/esp/src/lws.config.js +++ b/esp/src/lws.config.js @@ -1,6 +1,6 @@ const fs = require("fs"); -let ip = "192.168.99.103"; +let ip = "https://play.hpccsystems.com:18010"; if (fs.existsSync("./lws.target.txt")) { ip = fs.readFileSync("./lws.target.txt").toString().replace("\r\n", "\n").split("\n")[0]; } @@ -68,5 +68,5 @@ let rewrite = [ module.exports = { port: 8080, rewrite: rewrite, - stack: ['lws-basic-auth', 'lws-request-monitor', 'lws-log', 'lws-cors', 'lws-json', 'lws-compress', 'lws-rewrite', 'lws-blacklist', 'lws-conditional-get', 'lws-mime', 'lws-range', 'lws-spa', 'lws-static', 'lws-index'] + stack: ["lws-basic-auth", "lws-request-monitor", "lws-log", "lws-cors", "lws-json", "lws-compress", "lws-rewrite", "lws-blacklist", "lws-conditional-get", "lws-mime", "lws-range", "lws-spa", "lws-static", "lws-index"] }; \ No newline at end of file diff --git a/esp/src/package-lock.json b/esp/src/package-lock.json index 4fcce04e973..7571e9c3011 100644 --- a/esp/src/package-lock.json +++ b/esp/src/package-lock.json @@ -57,13 +57,16 @@ "xstyle": "0.3.3" }, "devDependencies": { + "@playwright/test": "^1.49.0", "@simbathesailor/use-what-changed": "^2.0.0", "@types/dojo": "1.9.48", + "@types/node": "^22.10.1", "@types/react": "17.0.80", "@types/react-dom": "17.0.25", "@typescript-eslint/eslint-plugin": "6.21.0", "@typescript-eslint/parser": "6.21.0", "copyfiles": "2.4.1", + "cross-env": "^7.0.3", "css-loader": "6.10.0", "dojo-webpack-plugin": "3.0.6", "eslint": "8.57.0", @@ -2626,6 +2629,21 @@ "openid-client": "^5.3.0" } }, + "node_modules/@kubernetes/client-node/node_modules/@types/node": { + "version": "20.17.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz", + "integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@kubernetes/client-node/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -3143,6 +3161,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz", + "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.49.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@simbathesailor/use-what-changed": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@simbathesailor/use-what-changed/-/use-what-changed-2.0.0.tgz", @@ -3442,11 +3476,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", - "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.20.0" } }, "node_modules/@types/node-forge": { @@ -5078,6 +5113,25 @@ "node": ">=10" } }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -9371,6 +9425,53 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", + "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.49.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz", + "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.4.33", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", @@ -11539,9 +11640,10 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" }, "node_modules/universal-github-app-jwt": { "version": "1.1.1", diff --git a/esp/src/package.json b/esp/src/package.json index db7708ef45d..6b09650070c 100644 --- a/esp/src/package.json +++ b/esp/src/package.json @@ -28,8 +28,10 @@ "dev-start": "run-p bundle-watch dev-start-ws", "dev-start-verbose": "ws --verbose.include request response", "rm-hpcc": "rimraf ./node_modules/@hpcc-js", - "start": "webpack serve --env development --config webpack.config.js", - "test": "run-s lint", + "start": "ws", + "test": "cross-env npx playwright test", + "test-codegen": "cross-env npx playwright codegen", + "test-interactive": "cross-env npx playwright test --ui", "update": "npx npm-check-updates -u -t minor", "update-major": "npx npm-check-updates -u" }, @@ -83,13 +85,16 @@ "xstyle": "0.3.3" }, "devDependencies": { + "@playwright/test": "^1.49.0", "@simbathesailor/use-what-changed": "^2.0.0", "@types/dojo": "1.9.48", + "@types/node": "^22.10.1", "@types/react": "17.0.80", "@types/react-dom": "17.0.25", "@typescript-eslint/eslint-plugin": "6.21.0", "@typescript-eslint/parser": "6.21.0", "copyfiles": "2.4.1", + "cross-env": "^7.0.3", "css-loader": "6.10.0", "dojo-webpack-plugin": "3.0.6", "eslint": "8.57.0", diff --git a/esp/src/playwright.config.ts b/esp/src/playwright.config.ts new file mode 100644 index 00000000000..0dc85e88477 --- /dev/null +++ b/esp/src/playwright.config.ts @@ -0,0 +1,47 @@ +import { defineConfig, devices } from "@playwright/test"; + +const port = process.env.CI ? "8010" : "8080"; +const baseURL = process.env.BASE_URL ?? `http://127.0.0.1:${port}`; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 4 : undefined, + reporter: "html", + use: { + baseURL, + trace: "on-first-retry", + ignoreHTTPSErrors: true + }, + + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: "npm run start", + url: baseURL, + reuseExistingServer: true, + ignoreHTTPSErrors: true, + }, +}); diff --git a/esp/src/tests/eclwatch-v5.spec.ts b/esp/src/tests/eclwatch-v5.spec.ts new file mode 100644 index 00000000000..79601fcaf21 --- /dev/null +++ b/esp/src/tests/eclwatch-v5.spec.ts @@ -0,0 +1,34 @@ +import { test, expect } from "@playwright/test"; + +test.describe("ECLWatch V5", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/esp/files/index.html"); + await page.evaluate(() => { + sessionStorage.setItem("ECLWatch:ModernMode-9.0", "false"); + }); + }); + + test("Basic Frame", async ({ page }) => { + await page.goto("/esp/files/stub.htm"); + await expect(page.locator("#stubStackController_stub_Main span").first()).toBeVisible(); + await expect(page.getByLabel("Advanced")).toBeVisible(); + }); + + test("Activities", async ({ page }) => { + await page.goto("/esp/files/stub.htm"); + await expect(page.locator("#stub_Main-DLStackController_stub_Main-DL_Activity_label")).toBeVisible(); + await expect(page.getByLabel("Auto Refresh")).toBeVisible(); + await expect(page.getByLabel("Maximize/Restore")).toBeVisible(); + await expect(page.locator("i")).toBeVisible(); + await expect(page.locator("svg").filter({ hasText: "%hthor" })).toBeVisible(); + await expect(page.locator("svg").filter({ hasText: /^0%thor$/ })).toBeVisible(); + await expect(page.locator("svg").filter({ hasText: "%roxie" })).toBeVisible(); + await expect(page.locator("svg").filter({ hasText: "%thor_roxie" })).toBeVisible(); + await expect(page.getByRole("img", { name: "Priority" })).toBeVisible(); + await expect(page.getByText("Target/Wuid")).toBeVisible(); + await expect(page.getByText("Graph")).toBeVisible(); + await expect(page.getByText("State")).toBeVisible(); + await expect(page.getByText("Owner")).toBeVisible(); + await expect(page.getByText("Job Name")).toBeVisible(); + }); +}); diff --git a/esp/src/tests/eclwatch-v9.spec.ts b/esp/src/tests/eclwatch-v9.spec.ts new file mode 100644 index 00000000000..aeb5dbde876 --- /dev/null +++ b/esp/src/tests/eclwatch-v9.spec.ts @@ -0,0 +1,44 @@ +import { test, expect } from "@playwright/test"; + +test.describe("ECLWatch V9", () => { + + test("Basic Frame", async ({ page }) => { + await page.goto("/esp/files/index.html#/activities"); + await expect(page.getByRole("link", { name: "ECL Watch" })).toBeVisible(); + await expect(page.locator("button").filter({ hasText: "" })).toBeVisible(); + await expect(page.getByRole("button", { name: "Advanced" })).toBeVisible(); + await expect(page.getByTitle("Activities")).toBeVisible(); + await expect(page.getByRole("link", { name: "ECL", exact: true })).toBeVisible(); + await expect(page.getByRole("link", { name: "Files" })).toBeVisible(); + await expect(page.getByRole("link", { name: "Published Queries" })).toBeVisible(); + await expect(page.getByRole("button", { name: "History" })).toBeVisible(); + await expect(page.getByRole("button", { name: "Add to favorites" })).toBeVisible(); + await expect(page.locator("a").filter({ hasText: /^Activities$/ })).toBeVisible(); + await expect(page.getByRole("link", { name: "Event Scheduler" })).toBeVisible(); + }); + + test("Activities", async ({ page }) => { + await page.goto("/esp/files/index.html#/activities"); + await page.getByTitle("Disk Usage").locator("i").click(); + await expect(page.locator("svg").filter({ hasText: "%hthor" })).toBeVisible(); + await expect(page.locator("svg").filter({ hasText: /^0%thor$/ })).toBeVisible(); + await expect(page.locator("svg").filter({ hasText: "%roxie" })).toBeVisible(); + await expect(page.locator(".reflex-splitter")).toBeVisible(); + await expect(page.getByRole("menubar")).toBeVisible(); + await expect(page.getByRole("menuitem", { name: "Refresh" })).toBeVisible(); + await expect(page.locator("button").filter({ hasText: "" })).toBeVisible(); + await expect(page.locator("button").filter({ hasText: "" })).toBeVisible(); + await expect(page.getByRole("columnheader", { name: "Priority" }).locator("div").first()).toBeVisible(); + await expect(page.getByText("Target/Wuid")).toBeVisible(); + await expect(page.getByText("Graph")).toBeVisible(); + await expect(page.getByText("State")).toBeVisible(); + await expect(page.getByText("Owner")).toBeVisible(); + await expect(page.getByText("Job Name")).toBeVisible(); + await expect(page.getByRole("gridcell", { name: "HThorServer - hthor" })).toBeVisible(); + await expect(page.getByRole("gridcell", { name: "ThorMaster - thor", exact: true })).toBeVisible(); + await expect(page.getByRole("gridcell", { name: "ThorMaster - thor_roxie" })).toBeVisible(); + await expect(page.getByRole("gridcell", { name: "RoxieServer - roxie" })).toBeVisible(); + await expect(page.getByRole("gridcell", { name: "myeclccserver - hthor." })).toBeVisible(); + await expect(page.getByRole("gridcell", { name: "mydfuserver - dfuserver_queue" })).toBeVisible(); + }); +});