From a175e18657fd598a418372f9414c26dd29c980e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Mon, 2 Mar 2026 11:18:43 +0100 Subject: [PATCH 01/13] implement frontend linting --- .github/workflows/frontend-linting.yml | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/frontend-linting.yml diff --git a/.github/workflows/frontend-linting.yml b/.github/workflows/frontend-linting.yml new file mode 100644 index 00000000..f31c2cd2 --- /dev/null +++ b/.github/workflows/frontend-linting.yml @@ -0,0 +1,30 @@ +name: frontend-linting-and-formatting + +on: + push: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + lint: + runs-on: ubuntu-22.04 + steps: + - name: Checkout frontend sources + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Display Node version + run: node -v && npm -v + + - name: Install frontend dependencies + run: npm ci + + - name: Run code formatting + run: npm run format + + - name: Run linting + run: npm run lint \ No newline at end of file From dd5b20af4c26a2e709bd239ba237a25e1fdbc8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Mon, 2 Mar 2026 11:29:09 +0100 Subject: [PATCH 02/13] fix working directory at frontend linting --- .github/workflows/frontend-linting.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/frontend-linting.yml b/.github/workflows/frontend-linting.yml index f31c2cd2..1bf4f54f 100644 --- a/.github/workflows/frontend-linting.yml +++ b/.github/workflows/frontend-linting.yml @@ -21,10 +21,13 @@ jobs: run: node -v && npm -v - name: Install frontend dependencies + working-directory: frontend run: npm ci - name: Run code formatting + working-directory: frontend run: npm run format - name: Run linting + working-directory: frontend run: npm run lint \ No newline at end of file From 03a4824a48794fbfee7789ed46f03e22c241243f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Mon, 2 Mar 2026 13:13:32 +0100 Subject: [PATCH 03/13] implement frontend build and packaging tests --- .../frontend-build-and-packaging.yml | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/frontend-build-and-packaging.yml diff --git a/.github/workflows/frontend-build-and-packaging.yml b/.github/workflows/frontend-build-and-packaging.yml new file mode 100644 index 00000000..f41cb0aa --- /dev/null +++ b/.github/workflows/frontend-build-and-packaging.yml @@ -0,0 +1,29 @@ +name: frontend-build-and-packaging + +on: + push: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + build-and-packaging: + runs-on: ubuntu-22.04 + steps: + - name: Checkout frontend sources + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Display Node version + run: node -v && npm -v + + - name: Install frontend dependencies + working-directory: frontend + run: npm ci + + - name: Run build and package + working-directory: frontend + run: npm run package \ No newline at end of file From d35dce2b4d2bc734fc1e3d751e16c310b6bb7ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Mon, 2 Mar 2026 13:45:35 +0100 Subject: [PATCH 04/13] implement e2e tests in ci --- .github/workflows/frontend-e2e-tests.yml | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/frontend-e2e-tests.yml diff --git a/.github/workflows/frontend-e2e-tests.yml b/.github/workflows/frontend-e2e-tests.yml new file mode 100644 index 00000000..06dafbdb --- /dev/null +++ b/.github/workflows/frontend-e2e-tests.yml @@ -0,0 +1,35 @@ +name: frontend-e2e-tests + +on: + push: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + e2e: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Install dependencies + working-directory: frontend + run: npm ci + + - name: Install Xvfb + run: sudo apt-get update && sudo apt-get install -y xvfb + + - name: Run E2E tests + working-directory: frontend + run: | + xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" bash -c " + npm run start:e2e & + sleep 30 + npm run test:e2e + " \ No newline at end of file From 98d697241157e11c19f4834bf913a4560e506675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Mon, 2 Mar 2026 14:25:15 +0100 Subject: [PATCH 05/13] fix to launch backend --- .github/workflows/frontend-e2e-tests.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/frontend-e2e-tests.yml b/.github/workflows/frontend-e2e-tests.yml index 06dafbdb..91116fef 100644 --- a/.github/workflows/frontend-e2e-tests.yml +++ b/.github/workflows/frontend-e2e-tests.yml @@ -13,6 +13,13 @@ jobs: - name: Checkout sources uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + + - name: Display Python version + run: python -c "import sys; print(sys.version)" + - name: Setup Node.js uses: actions/setup-node@v3 with: @@ -30,6 +37,6 @@ jobs: run: | xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" bash -c " npm run start:e2e & - sleep 30 + sleep 120 npm run test:e2e " \ No newline at end of file From 83c8d2e09307d67fccd5f7ca871389c4bf2197c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Mon, 2 Mar 2026 15:19:21 +0100 Subject: [PATCH 06/13] start e2e tests with venv --- .github/workflows/frontend-e2e-tests.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/frontend-e2e-tests.yml b/.github/workflows/frontend-e2e-tests.yml index 91116fef..31c5104a 100644 --- a/.github/workflows/frontend-e2e-tests.yml +++ b/.github/workflows/frontend-e2e-tests.yml @@ -16,6 +16,8 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 + with: + python-version: '3.13' - name: Display Python version run: python -c "import sys; print(sys.version)" @@ -36,7 +38,13 @@ jobs: working-directory: frontend run: | xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" bash -c " + python3 -m venv ../ibex_venv + source ../ibex_venv/bin/activate + pip install --upgrade pip setuptools wheel + cd ../backend + pip install -e . + cd ../frontend npm run start:e2e & - sleep 120 + npx wait-on tcp:9222 npm run test:e2e " \ No newline at end of file From 0dcc83782fa116b38b945e06269db40d4a1cb491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Tue, 3 Mar 2026 14:43:50 +0100 Subject: [PATCH 07/13] fix the port conflict at launch --- frontend/src/config/config.ts | 66 +++++++++++++++++------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/frontend/src/config/config.ts b/frontend/src/config/config.ts index 5550c975..df5726cf 100644 --- a/frontend/src/config/config.ts +++ b/frontend/src/config/config.ts @@ -8,35 +8,12 @@ export type TConfig = { LOGGER_PORT: number; }; -const defaultConfig: TConfig = { - API_URL: 'http://localhost:8000', - WEBPACK_PORT: 3001, - LOGGER_PORT: 9013, -}; - -const getConfigPath = (): string => { - const configDir = path.join(os.homedir(), '.config', 'ibex'); - const configPath = path.join(configDir, 'config.json'); - - if (!fs.existsSync(configDir)) { - fs.mkdirSync(configDir, { recursive: true }); - } - - return configPath; -}; - -export const getConfigSync = (): TConfig => { - // Check if backend URL is provided by Electron main process - const backendUrl = process.env.IBEX_BACKEND_URL; - - if (backendUrl) { - console.info(`Using backend URL from Electron: ${backendUrl}`); - return { - ...defaultConfig, - API_URL: backendUrl, - }; - } - +const createDefaultConfig = (): TConfig => { + const defaultConfig = { + API_URL: 'http://localhost:8000', + WEBPACK_PORT: 3001, + LOGGER_PORT: Math.floor(49152 + Math.random() * (65535 - 49152)), + } as TConfig; const configPath = getConfigPath(); try { @@ -49,7 +26,7 @@ export const getConfigSync = (): TConfig => { } const data = fs.readFileSync(configPath, 'utf-8'); - const parsed = JSON.parse(data); + const parsed = JSON.parse(data) as TConfig; if ( typeof parsed.API_URL === 'string' && @@ -59,10 +36,33 @@ export const getConfigSync = (): TConfig => { return parsed; } - console.warn('Configuration invalide, retour à la config par défaut.'); - return defaultConfig; + console.warn('Read invalid configuration, get the default configuration.'); + return defaultConfig as TConfig; } catch (err) { - console.error('Erreur de lecture de config:', err); + console.error('Error reading the configuration file:', err); return defaultConfig; } }; + +const getConfigPath = (): string => { + const configDir = path.join(os.homedir(), '.config', 'ibex'); + const configPath = path.join(configDir, 'config.json'); + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }); + } + return configPath; +}; + +export const getConfigSync = (): TConfig => { + // Check if backend URL is provided by Electron main process + const backendUrl = process.env.IBEX_BACKEND_URL; + if (backendUrl) { + console.info(`Using backend URL from Electron: ${backendUrl}`); + return { + ...createDefaultConfig(), + API_URL: backendUrl, + }; + } else { + return { ...createDefaultConfig() }; + } +}; From 7478dce3c45f68f0b4b385ce536ea9fdad9629a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Tue, 3 Mar 2026 16:15:19 +0100 Subject: [PATCH 08/13] Replace inexistant URI to pass the E2E tests --- frontend/src/tests/plot-ui.spec.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/src/tests/plot-ui.spec.ts b/frontend/src/tests/plot-ui.spec.ts index 2c1e7d67..e052243f 100644 --- a/frontend/src/tests/plot-ui.spec.ts +++ b/frontend/src/tests/plot-ui.spec.ts @@ -20,7 +20,7 @@ import { expect } from 'chai'; /** * UI Test Suite for the Visualization Component */ -describe('UI Tests for Header Component', function () { +describe('UI Tests for plotted data', function () { this.timeout(60000); before(async () => { @@ -75,12 +75,12 @@ describe('UI Tests for Header Component', function () { ); /// - /// Add the URI 'imas:hdf5?user=public;pulse=100002;run=1;database=iterdb;version=3' to the configuration and navigate in the accordion node tree + /// Add the URI 'imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106' to the configuration and navigate in the accordion node tree /// await ensureCssElementIsDisplayed('config-uri-selection-modal'); await writeTextInCssElement( 'config-uri-selection-modal-uri-text-input', - 'imas:hdf5?user=public;pulse=100002;run=1;database=iterdb;version=3', + 'imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', ); await findCssElementAndClickIt('config-uri-selection-modal-add-uri-button'); await findCssElementAndClickIt( @@ -89,27 +89,27 @@ describe('UI Tests for Header Component', function () { 300, ); await ensureCssElementIsDisplayed( - 'uriAccordion-imas:hdf5?user=public;pulse=100002;run=1;database=iterdb;version=3', + 'uriAccordion-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', 200, 100, ); await findCssElementAndClickIt( - 'uriAccordion-imas:hdf5?user=public;pulse=100002;run=1;database=iterdb;version=3', + 'uriAccordion-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', 200, 100, ); await findCssElementAndClickIt( - 'folder-imas:hdf5?user=public;pulse=100002;run=1;database=iterdb;version=3#core_profiles:0/', + 'folder-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/', 200, 100, ); await findCssElementAndClickIt( - 'folder-imas:hdf5?user=public;pulse=100002;run=1;database=iterdb;version=3#core_profiles:0/profiles_1d[:]/', + 'folder-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/', 200, 100, ); await findCssElementAndClickIt( - 'folder-imas:hdf5?user=public;pulse=100002;run=1;database=iterdb;version=3#core_profiles:0/profiles_1d[:]/ion[:]/', + 'folder-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/ion[:]/', 200, 100, ); @@ -124,7 +124,7 @@ describe('UI Tests for Header Component', function () { ); // Click on temperature checkbox to start a new plot await findCssElementAndClickIt( - 'checkbox-imas:hdf5?user=public;pulse=100002;run=1;database=iterdb;version=3#core_profiles:0/profiles_1d[:]/ion[:]/temperature', + 'checkbox-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/ion[:]/temperature', ); // Check that there is one dataplot created await waitForValue( @@ -148,7 +148,7 @@ describe('UI Tests for Header Component', function () { ); // Click on density checkbox to plot a second axis await findCssElementAndClickIt( - 'checkbox-imas:hdf5?user=public;pulse=100002;run=1;database=iterdb;version=3#core_profiles:0/profiles_1d[:]/ion[:]/density', + 'checkbox-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/ion[:]/density', ); // Check that the Y plot is defined await waitForValue( @@ -195,14 +195,14 @@ describe('UI Tests for Header Component', function () { ); /// - /// Add the URI 'imas:hdf5?user=public;pulse=100002;run=1;database=iterdb;version=3' to the configuration and navigate in the accordion node tree + /// Add the URI 'imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106' to the configuration and navigate in the accordion node tree /// const uriModal = await ensureCssElementIsDisplayed( 'config-uri-selection-modal', ); await writeTextInCssElement( 'config-uri-selection-modal-uri-text-input', - 'imas:hdf5?user=public;pulse=100002;run=1;database=iterdb;version=3', + 'imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', true, ); await findCssElementAndClickIt('config-uri-selection-modal-add-uri-button'); From bcfce16c7c57e2a556956efa5f0442275ee1c563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Thu, 5 Mar 2026 13:58:24 +0100 Subject: [PATCH 09/13] fix packages to launch E2E tests --- frontend/package-lock.json | 337 +------------------------------------ frontend/package.json | 2 +- 2 files changed, 10 insertions(+), 329 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 247f579c..ae1bda1c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,7 @@ "@mantine/notifications": "7.17.7", "@tabler/icons-react": "3.23.0", "@tensorflow/tfjs": "4.22.0", + "axios": "1.13.6", "dotenv": "16.4.7", "electron-default-menu": "1.0.2", "electron-squirrel-startup": "1.0.1", @@ -50,7 +51,6 @@ "@types/selenium-webdriver": "4.1.28", "@vercel/webpack-asset-relocator-loader": "1.7.3", "chai": "4.3.10", - "chromedriver": "134.0.5", "cross-env": "7.0.3", "css-loader": "6.0.0", "electron": "35.7.5", @@ -1967,12 +1967,6 @@ "node": ">=10" } }, - "node_modules/@testim/chrome-version": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.4.tgz", - "integrity": "sha512-kIhULpw9TrGYnHp/8VfdcneIcxKnLixmADtukQRtJUmsVlMg0niMkwV0xZmi8hqa57xqilIHjWFA0GKvEjVU5g==", - "dev": true - }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -1982,12 +1976,6 @@ "node": ">= 10" } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true - }, "node_modules/@tsconfig/node10": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", @@ -3432,18 +3420,6 @@ "node": "*" } }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -3492,13 +3468,12 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", - "dev": true, + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -3545,15 +3520,6 @@ "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -4096,28 +4062,6 @@ "node": ">=6.0" } }, - "node_modules/chromedriver": { - "version": "134.0.5", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-134.0.5.tgz", - "integrity": "sha512-edXbiuShAvH6Elx8Hobl4NQkgNRMIozcW7ZlEiE8TBynZHRazrepO9hfftQzZgztPvjMQiSWeWjZaDV3SecYaw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@testim/chrome-version": "^1.1.4", - "axios": "^1.7.4", - "compare-versions": "^6.1.0", - "extract-zip": "^2.0.1", - "proxy-agent": "^6.4.0", - "proxy-from-env": "^1.1.0", - "tcp-port-used": "^1.0.2" - }, - "bin": { - "chromedriver": "bin/chromedriver" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/clamp": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz", @@ -4445,12 +4389,6 @@ "node": ">=0.10.0" } }, - "node_modules/compare-versions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", - "dev": true - }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -5031,15 +4969,6 @@ "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==", "peer": true }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -5321,20 +5250,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6647,6 +6562,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "peer": true, "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -6936,6 +6852,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "peer": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -7505,7 +7422,6 @@ "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "dev": true, "funding": [ { "type": "individual", @@ -7963,20 +7879,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "dev": true, - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/gl-mat4": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gl-mat4/-/gl-mat4-1.2.0.tgz", @@ -9053,15 +8955,6 @@ "node": ">= 12" } }, - "node_modules/ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -9557,12 +9450,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true - }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -9618,20 +9505,6 @@ "node": ">=8" } }, - "node_modules/is2": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", - "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" - }, - "engines": { - "node": ">=v0.10.0" - } - }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -10831,15 +10704,6 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -11429,87 +11293,6 @@ "node": ">=4" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -12252,79 +12035,10 @@ "node": ">= 0.10" } }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/pump": { "version": "3.0.3", @@ -14638,39 +14352,6 @@ "node": ">=8" } }, - "node_modules/tcp-port-used": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", - "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", - "dev": true, - "dependencies": { - "debug": "4.3.1", - "is2": "^2.0.6" - } - }, - "node_modules/tcp-port-used/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/tcp-port-used/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/temp": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index f5809eff..afa3fe86 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,7 +36,6 @@ "@types/selenium-webdriver": "4.1.28", "@vercel/webpack-asset-relocator-loader": "1.7.3", "chai": "4.3.10", - "chromedriver": "134.0.5", "cross-env": "7.0.3", "css-loader": "6.0.0", "electron": "35.7.5", @@ -72,6 +71,7 @@ "@mantine/notifications": "7.17.7", "@tabler/icons-react": "3.23.0", "@tensorflow/tfjs": "4.22.0", + "axios": "1.13.6", "dotenv": "16.4.7", "electron-default-menu": "1.0.2", "electron-squirrel-startup": "1.0.1", From 6623535b298538198e0355225193f9d3ee9a3e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Thu, 5 Mar 2026 14:11:35 +0100 Subject: [PATCH 10/13] Add E2E test to verify data integrity when applying ranges --- .../renderer/components/grid/HoverButtons.tsx | 1 + .../components/tabs/TabsListCustom.tsx | 1 + .../visualization/DataplotCustomization.tsx | 6 +- .../CustomizeDataRange.tsx | 12 + frontend/src/tests/plot-ui.spec.ts | 298 +++++++++++++++++- 5 files changed, 316 insertions(+), 2 deletions(-) diff --git a/frontend/src/renderer/components/grid/HoverButtons.tsx b/frontend/src/renderer/components/grid/HoverButtons.tsx index 20c72643..bd592ece 100644 --- a/frontend/src/renderer/components/grid/HoverButtons.tsx +++ b/frontend/src/renderer/components/grid/HoverButtons.tsx @@ -249,6 +249,7 @@ export const HoverButtons = React.memo( handleCustomization(data.i)} className={classes.actionButton} > diff --git a/frontend/src/renderer/components/tabs/TabsListCustom.tsx b/frontend/src/renderer/components/tabs/TabsListCustom.tsx index 9b1af74e..0e8565de 100644 --- a/frontend/src/renderer/components/tabs/TabsListCustom.tsx +++ b/frontend/src/renderer/components/tabs/TabsListCustom.tsx @@ -52,6 +52,7 @@ export const TabsListCustom = ({ saveAndClose()} > diff --git a/frontend/src/renderer/pages/visualization/DataplotCustomization.tsx b/frontend/src/renderer/pages/visualization/DataplotCustomization.tsx index b530030a..4497c39f 100644 --- a/frontend/src/renderer/pages/visualization/DataplotCustomization.tsx +++ b/frontend/src/renderer/pages/visualization/DataplotCustomization.tsx @@ -166,7 +166,11 @@ const Customization = ({ opened={item?.disabled ? null : false} > - + {item.value} {item.component} diff --git a/frontend/src/renderer/pages/visualization/customizableElements/CustomizeDataRange.tsx b/frontend/src/renderer/pages/visualization/customizableElements/CustomizeDataRange.tsx index 90edb56a..76fa4ed2 100644 --- a/frontend/src/renderer/pages/visualization/customizableElements/CustomizeDataRange.tsx +++ b/frontend/src/renderer/pages/visualization/customizableElements/CustomizeDataRange.tsx @@ -305,6 +305,9 @@ export const CustomizeDataRange = ({ onChange={(value: number) => setTypedMinRange(value)} w={150} hideControls + data-testid={ + coordinate.axeIndex === 0 ? 'data-range-min-input' : undefined + } /> setTypedMaxRange(value)} w={150} hideControls + data-testid={ + coordinate.axeIndex === 0 ? 'data-range-max-input' : undefined + } /> ) : ( @@ -354,6 +360,9 @@ export const CustomizeDataRange = ({ } loading={isLoadingApply} leftSection={} + data-testid={ + coordinate.axeIndex === 0 ? 'data-range-apply-input' : undefined + } > Apply @@ -363,6 +372,9 @@ export const CustomizeDataRange = ({ loading={isLoadingRestore} variant="outline" leftSection={} + data-testid={ + coordinate.axeIndex === 0 ? 'data-range-restore-input' : undefined + } > Restore diff --git a/frontend/src/tests/plot-ui.spec.ts b/frontend/src/tests/plot-ui.spec.ts index e052243f..e207d920 100644 --- a/frontend/src/tests/plot-ui.spec.ts +++ b/frontend/src/tests/plot-ui.spec.ts @@ -11,6 +11,7 @@ import { ensureCssElementIsDisplayed, findCssElementAndClickIt, findTextElementAndClickIt, + getCssElementFromDataTestId, waitForElementToDisappear, waitForValue, writeTextInCssElement, @@ -21,7 +22,7 @@ import { expect } from 'chai'; * UI Test Suite for the Visualization Component */ describe('UI Tests for plotted data', function () { - this.timeout(60000); + this.timeout(90000); before(async () => { await startApp(); @@ -166,6 +167,301 @@ describe('UI Tests for plotted data', function () { ); }); + it('Should crop the data in all plots by applying a data range', async () => { + /// + /// Create a new configuration named 'New Plot Config' + /// + await findCssElementAndClickIt('header-add-configuration'); + const configCreateModal = await ensureCssElementIsDisplayed( + 'config-create-modal', + ); + await writeTextInCssElement('config-create-name-input', 'New Plot Config'); + await findCssElementAndClickIt('config-create-submit-button'); + await waitForElementToDisappear(configCreateModal); + await waitForValue( + 'Plot configuration length', + async () => (await getTestState()).configurations.length, + 1, + ); + await waitForValue( + 'Plot configuration name', + async () => (await getTestState()).configurations[0].name, + 'New Plot Config', + ); + + /// + /// Add the URI 'imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106' to the configuration and navigate in the accordion node tree + /// + await ensureCssElementIsDisplayed('config-uri-selection-modal'); + await writeTextInCssElement( + 'config-uri-selection-modal-uri-text-input', + 'imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', + true, + ); + await findCssElementAndClickIt('config-uri-selection-modal-add-uri-button'); + await findCssElementAndClickIt( + 'config-uri-selection-modal-validate-button', + 100, + 300, + ); + await ensureCssElementIsDisplayed( + 'uriAccordion-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', + 200, + 100, + ); + await findCssElementAndClickIt( + 'uriAccordion-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', + 200, + 100, + ); + await findCssElementAndClickIt( + 'folder-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/', + 200, + 100, + ); + await findCssElementAndClickIt( + 'folder-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/', + 200, + 100, + ); + + /// + /// The accordion node tree is now unfold, check that the plot are correctly added into the active configuration + /// + await waitForValue( + 'DataPlot configuration length', + async () => (await getTestState()).active.dataPlot.length, + 0, + ); + // Click on j_total checkbox to start a new plot + await findCssElementAndClickIt( + 'checkbox-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/j_total', + ); + // Check that there is one dataplot created + await waitForValue( + 'DataPlot configuration length', + async () => (await getTestState()).active.dataPlot.length, + 1, + ); + // Click on j_ohmic checkbox to plot a second data + await findCssElementAndClickIt( + 'checkbox-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/j_ohmic', + ); + // Check that there is two dataplot created + await waitForValue( + 'Plot configuration length', + async () => (await getTestState()).active.dataPlot[0].plot.length, + 2, + ); + // Now we check integrity of data manipulation + // Step 1 - The minimum and maximum original data for the two plots + const originalDataFromActive = (await getTestState()).active; + await waitForValue( + 'First y value of j_total at origin', + async () => originalDataFromActive.dataPlot[0]?.plot[0]?.y[0], + -191505.77227601665, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'Last y value of j_total at origin', + async () => + originalDataFromActive.dataPlot[0]?.plot[0]?.y[ + originalDataFromActive.dataPlot[0]?.plot[0]?.y.length - 1 + ], + -12283.374007355182, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'First y value of j_ohmic at origin', + async () => originalDataFromActive.dataPlot[0]?.plot[1]?.y[0], + -155459.99347997818, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'Last y value of j_ohmic at origin', + async () => + originalDataFromActive.dataPlot[0]?.plot[1]?.y[ + originalDataFromActive.dataPlot[0]?.plot[1]?.y.length - 1 + ], + -14479.974999151873, + (actual, expected) => actual === expected, + ); + + // Step 2 - Enter the data range, apply a range, confirm the change, and check the values + await findCssElementAndClickIt('customization-access-button'); + await findCssElementAndClickIt('customization-Axis range-accordion'); + await writeTextInCssElement('data-range-min-input', '0.2', true); + await writeTextInCssElement('data-range-max-input', '0.8', true); + await findCssElementAndClickIt('data-range-apply-input'); + await findCssElementAndClickIt('customization-save-button'); + const activeAtFirstApplied = (await getTestState()).active; + await waitForValue( + 'First y value of j_total at first applied', + async () => activeAtFirstApplied.dataPlot[0]?.plot[0]?.y[0], + -200577.796875, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'Last y value of j_total at first applied', + async () => + activeAtFirstApplied.dataPlot[0]?.plot[0]?.y[ + activeAtFirstApplied.dataPlot[0]?.plot[0]?.y.length - 1 + ], + -102633.421875, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'First y value of j_ohmic at first applied', + async () => activeAtFirstApplied.dataPlot[0]?.plot[1]?.y[0], + -158003.015625, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'Last y value of j_ohmic at first applied', + async () => + activeAtFirstApplied.dataPlot[0]?.plot[1]?.y[ + activeAtFirstApplied.dataPlot[0]?.plot[1]?.y.length - 1 + ], + -95764.546875, + (actual, expected) => actual === expected, + ); + + // Step 3 - Enter the data range, apply a second range, confirm the change, and check the values + await findCssElementAndClickIt('customization-access-button'); + await findCssElementAndClickIt('customization-Axis range-accordion'); + await writeTextInCssElement('data-range-min-input', '0.4', true); + await writeTextInCssElement('data-range-max-input', '0.6', true); + await findCssElementAndClickIt('data-range-apply-input'); + await findCssElementAndClickIt('customization-save-button'); + const activeAtSecondApplied = (await getTestState()).active; + await waitForValue( + 'First y value of j_total at second applied', + async () => activeAtSecondApplied.dataPlot[0]?.plot[0]?.y[0], + -220957.3125, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'Last y value of j_total at second applied', + async () => + activeAtSecondApplied.dataPlot[0]?.plot[0]?.y[ + activeAtSecondApplied.dataPlot[0]?.plot[0]?.y.length - 1 + ], + -195015.078125, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'First y value of j_ohmic at second applied', + async () => activeAtSecondApplied.dataPlot[0]?.plot[1]?.y[0], + -200219.234375, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'Last y value of j_ohmic at second applied', + async () => + activeAtSecondApplied.dataPlot[0]?.plot[1]?.y[ + activeAtSecondApplied.dataPlot[0]?.plot[1]?.y.length - 1 + ], + -182456.90625, + (actual, expected) => actual === expected, + ); + + // Step 4 - Enter the data range, apply a third, wider range, confirm the change, and check the values + await findCssElementAndClickIt('customization-access-button'); + await findCssElementAndClickIt('customization-Axis range-accordion'); + await writeTextInCssElement('data-range-min-input', '0.2', true); + await writeTextInCssElement('data-range-max-input', '0.8', true); + await findCssElementAndClickIt('data-range-apply-input'); + await waitForValue( + 'Restore button finished loading', + async () => + ( + await getCssElementFromDataTestId('data-range-apply-input') + ).getAttribute('data-loading'), + null, + (actual, expected) => actual === expected, + 10, + ); + await findCssElementAndClickIt('customization-save-button'); + const activeAtThirdApplied = (await getTestState()).active; + await waitForValue( + 'First y value of j_total at third applied', + async () => activeAtThirdApplied.dataPlot[0]?.plot[0]?.y[0], + -200577.796875, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'Last y value of j_total at third applied', + async () => + activeAtThirdApplied.dataPlot[0]?.plot[0]?.y[ + activeAtThirdApplied.dataPlot[0]?.plot[0]?.y.length - 1 + ], + -102633.421875, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'First y value of j_ohmic at third applied', + async () => activeAtThirdApplied.dataPlot[0]?.plot[1]?.y[0], + -158003.015625, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'Last y value of j_ohmic at third applied', + async () => + activeAtThirdApplied.dataPlot[0]?.plot[1]?.y[ + activeAtThirdApplied.dataPlot[0]?.plot[1]?.y.length - 1 + ], + -95764.546875, + (actual, expected) => actual === expected, + ); + + // Step 5 - Enter the data range, restore the range, confirm the change, and check if the values have returned to their original state + await findCssElementAndClickIt('customization-access-button'); + await findCssElementAndClickIt('customization-Axis range-accordion'); + await findCssElementAndClickIt('data-range-restore-input'); + await waitForValue( + 'Restore button finished loading', + async () => + ( + await getCssElementFromDataTestId('data-range-restore-input') + ).getAttribute('data-loading'), + null, + (actual, expected) => actual === expected, + 10, + ); + await findCssElementAndClickIt('customization-save-button'); + const restoredDataFromActive = (await getTestState()).active; + await waitForValue( + 'First y value of j_total at restoration', + async () => restoredDataFromActive.dataPlot[0]?.plot[0]?.y[0], + -191505.765625, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'Last y value of j_total at restoration', + async () => + restoredDataFromActive.dataPlot[0]?.plot[0]?.y[ + restoredDataFromActive.dataPlot[0]?.plot[0]?.y.length - 1 + ], + -12283.3740234375, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'First y value of j_ohmic at restoration', + async () => restoredDataFromActive.dataPlot[0]?.plot[1]?.y[0], + -155460, + (actual, expected) => actual === expected, + ); + await waitForValue( + 'Last y value of j_ohmic at restoration', + async () => + restoredDataFromActive.dataPlot[0]?.plot[1]?.y[ + restoredDataFromActive.dataPlot[0]?.plot[1]?.y.length - 1 + ], + -14479.974609375, + (actual, expected) => actual === expected, + ); + }); + it('Should create a new configuration that follows a predefined template', async () => { /// /// Create a new configuration named 'New Plot Config' based on the 'PlotKineticProfilesIbexState.json' template From 1cbcb32b6c020a3d9e44f864ea1df6c2ee9d73f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Mon, 9 Mar 2026 13:30:58 +0100 Subject: [PATCH 11/13] fix E2E tests --- .github/workflows/frontend-e2e-tests.yml | 36 +++++ frontend/src/config/bridge.ts | 3 + frontend/src/main/ipc.ts | 6 +- frontend/src/preload.ts | 8 +- .../configuration/ConfigCreateModal.tsx | 3 + .../components/confirmation/ConfirmModal.tsx | 4 +- .../renderer/components/plot/SimplePlotly.tsx | 6 +- .../components/plot/hooks/usePlotLayout.ts | 4 +- .../preferences/PreferenceModal.tsx | 4 +- .../tree/TreeLibrariesAccordion.tsx | 6 +- .../visualization/DataplotCustomization.tsx | 6 +- .../visualization/VisualizationMetaData.tsx | 6 +- .../visualization/VisualizationURIModal.tsx | 3 + frontend/src/renderer/utils/plot.ts | 10 +- frontend/src/tests/plot-ui.spec.ts | 131 ++++++++++-------- frontend/src/tests/setup.ts | 2 +- frontend/src/tests/utils/testToolBox.ts | 38 ++--- zenodo_datasets.txt | 1 + 18 files changed, 179 insertions(+), 98 deletions(-) create mode 100644 zenodo_datasets.txt diff --git a/.github/workflows/frontend-e2e-tests.yml b/.github/workflows/frontend-e2e-tests.yml index 31c5104a..be5cf1d6 100644 --- a/.github/workflows/frontend-e2e-tests.yml +++ b/.github/workflows/frontend-e2e-tests.yml @@ -34,6 +34,40 @@ jobs: - name: Install Xvfb run: sudo apt-get update && sudo apt-get install -y xvfb + - name: Define dataset list and cache key + id: datasets + run: | + urls=($(cat zenodo_datasets.txt)) + echo "files=${urls[*]}" >> $GITHUB_OUTPUT + + # Generate a hash key from the URLs list, this is used for caching + key=$(echo "${urls[*]}" | sha256sum | cut -d ' ' -f1) + echo "key=${key}" >> $GITHUB_OUTPUT + + - name: Cache Zenodo datasets + id: cache-datasets + uses: actions/cache/restore@v4 + with: + path: e2e_datasets + key: ${{ steps.datasets.outputs.key }} + + - if: ${{ steps.cache-datasets.outputs.cache-hit != 'true' }} + name: Download datasets if not cached + run: | + mkdir -p e2e_datasets + for url in ${{ steps.datasets.outputs.files }}; do + echo "Downloading $(basename $url) ..." + wget -P e2e_datasets/ "$url" + done + + - name: Always save Zenodo datasets (even if pytest would fail) + id: cache-datasets-save + if: always() && steps.cache-datasets.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + key: ${{ steps.cache-datasets.outputs.cache-primary-key }} + path: e2e_datasets + - name: Run E2E tests working-directory: frontend run: | @@ -46,5 +80,7 @@ jobs: cd ../frontend npm run start:e2e & npx wait-on tcp:9222 + npx wait-on http://127.0.0.1:8000/docs/ + sleep 5 npm run test:e2e " \ No newline at end of file diff --git a/frontend/src/config/bridge.ts b/frontend/src/config/bridge.ts index f73011e3..51310935 100644 --- a/frontend/src/config/bridge.ts +++ b/frontend/src/config/bridge.ts @@ -4,5 +4,8 @@ import { API } from '../preload'; declare global { interface Window { api: typeof API; + env: { + E2E_TEST: string; + }; } } diff --git a/frontend/src/main/ipc.ts b/frontend/src/main/ipc.ts index 647940fb..0d4161f9 100644 --- a/frontend/src/main/ipc.ts +++ b/frontend/src/main/ipc.ts @@ -147,11 +147,11 @@ export default { return app.getPath('home'); }); - ipcMain.handle('getDefaultTemplatesPath', () => { + ipcMain.handle('getPathFromRessources', (event, folderSteps: string[]) => { const templatesPath = process.env.NODE_ENV === 'development' - ? path.join(app.getAppPath(), '..', 'templates') // root in dev - : path.join(process.resourcesPath, 'templates'); // resources/ in prod + ? path.join(app.getAppPath(), '..', ...folderSteps) // root in dev + : path.join(process.resourcesPath, ...folderSteps); // resources/ in prod return templatesPath; }); }, diff --git a/frontend/src/preload.ts b/frontend/src/preload.ts index 0c4dd73b..9710d84c 100644 --- a/frontend/src/preload.ts +++ b/frontend/src/preload.ts @@ -65,7 +65,10 @@ export const API = { getHomePath: async () => await ipcRenderer.invoke('getHomePath'), getDefaultTemplatesPath: async () => - await ipcRenderer.invoke('getDefaultTemplatesPath'), + await ipcRenderer.invoke('getPathFromRessources', ['templates']), + + getZenodoDataPath: async () => + await ipcRenderer.invoke('getPathFromRessources', ['e2e_datasets']), }, preferences: { @@ -107,3 +110,6 @@ export const API = { contextBridge.exposeInMainWorld('api', API); contextBridge.exposeInMainWorld('stubDataStorage', stubDataStorage); +contextBridge.exposeInMainWorld('env', { + E2E_TEST: process.env.E2E_TEST, +}); diff --git a/frontend/src/renderer/components/configuration/ConfigCreateModal.tsx b/frontend/src/renderer/components/configuration/ConfigCreateModal.tsx index 8bd85a40..728a2fac 100644 --- a/frontend/src/renderer/components/configuration/ConfigCreateModal.tsx +++ b/frontend/src/renderer/components/configuration/ConfigCreateModal.tsx @@ -212,6 +212,9 @@ export function ConfigCreateModal({ title="Create config" size="sm" data-testid="config-create-modal" + {...(window.env.E2E_TEST === 'true' && { + transitionProps: { duration: 0 }, + })} >
diff --git a/frontend/src/renderer/components/confirmation/ConfirmModal.tsx b/frontend/src/renderer/components/confirmation/ConfirmModal.tsx index 7e55647d..307622d0 100644 --- a/frontend/src/renderer/components/confirmation/ConfirmModal.tsx +++ b/frontend/src/renderer/components/confirmation/ConfirmModal.tsx @@ -19,8 +19,10 @@ export function ConfirmModal({ isOpen, onClose, onConfirm, children }: Props) { data-testid="confirm-modal" transitionProps={{ transition: 'fade', - duration: 0, }} + {...(window.env.E2E_TEST === 'true' && { + transitionProps: { duration: 0 }, + })} > { diff --git a/frontend/src/renderer/components/plot/SimplePlotly.tsx b/frontend/src/renderer/components/plot/SimplePlotly.tsx index e66e9051..1a862b33 100644 --- a/frontend/src/renderer/components/plot/SimplePlotly.tsx +++ b/frontend/src/renderer/components/plot/SimplePlotly.tsx @@ -53,8 +53,10 @@ export const SimplePlotly = ({ zeroline: false, type: (itemDataGrid?.xAxisData?.type as AxisType) || - typeof itemDataGrid.plot[0].x[0] === 'string' - ? 'category' + (itemDataGrid.plot.length > 0 && itemDataGrid.plot[0].x?.length > 0) + ? typeof itemDataGrid.plot[0]?.x[0] === 'string' + ? 'category' + : 'linear' : 'linear', exponentformat: 'power', showexponent: 'all', diff --git a/frontend/src/renderer/components/plot/hooks/usePlotLayout.ts b/frontend/src/renderer/components/plot/hooks/usePlotLayout.ts index a3e86f5d..d893f884 100644 --- a/frontend/src/renderer/components/plot/hooks/usePlotLayout.ts +++ b/frontend/src/renderer/components/plot/hooks/usePlotLayout.ts @@ -65,7 +65,9 @@ export function usePlotLayout({ }, [itemDataGrid.y2AxisData?.type, setLayoutPlot]); useEffect(() => { - const newTypeOfX = typeof itemDataGrid.plot[0].x[0]; + const newTypeOfX = itemDataGrid.plot[0]?.x + ? typeof itemDataGrid.plot[0]?.x[0] + : undefined; if (newTypeOfX === 'string') { // Update x axis to category type if it become a string setLayoutPlot((prevLayout) => ({ diff --git a/frontend/src/renderer/components/preferences/PreferenceModal.tsx b/frontend/src/renderer/components/preferences/PreferenceModal.tsx index 31e7654f..379afa51 100644 --- a/frontend/src/renderer/components/preferences/PreferenceModal.tsx +++ b/frontend/src/renderer/components/preferences/PreferenceModal.tsx @@ -24,8 +24,10 @@ export function PreferenceModal({ centered transitionProps={{ transition: 'fade', - duration: 0, }} + {...(window.env.E2E_TEST === 'true' && { + transitionProps: { duration: 0 }, + })} > {/* Main content */} diff --git a/frontend/src/renderer/components/tree/TreeLibrariesAccordion.tsx b/frontend/src/renderer/components/tree/TreeLibrariesAccordion.tsx index e347b0e1..3994790b 100644 --- a/frontend/src/renderer/components/tree/TreeLibrariesAccordion.tsx +++ b/frontend/src/renderer/components/tree/TreeLibrariesAccordion.tsx @@ -93,7 +93,11 @@ export const TreeLibrariesAccordion = ({ return ( - + {items} diff --git a/frontend/src/renderer/pages/visualization/DataplotCustomization.tsx b/frontend/src/renderer/pages/visualization/DataplotCustomization.tsx index 4497c39f..fd414a2a 100644 --- a/frontend/src/renderer/pages/visualization/DataplotCustomization.tsx +++ b/frontend/src/renderer/pages/visualization/DataplotCustomization.tsx @@ -184,7 +184,11 @@ const Customization = ({ Customize plot parameters - + {items} diff --git a/frontend/src/renderer/pages/visualization/VisualizationMetaData.tsx b/frontend/src/renderer/pages/visualization/VisualizationMetaData.tsx index 62101d3e..31aeaf57 100644 --- a/frontend/src/renderer/pages/visualization/VisualizationMetaData.tsx +++ b/frontend/src/renderer/pages/visualization/VisualizationMetaData.tsx @@ -125,7 +125,11 @@ const RenderMetaDataCoordinates = ({ {coordinates.length === 0 ? ( 'N/A' ) : ( - + {coordinates.map((coordinate, index) => ( {coordinate.name} diff --git a/frontend/src/renderer/pages/visualization/VisualizationURIModal.tsx b/frontend/src/renderer/pages/visualization/VisualizationURIModal.tsx index 22d316e0..f4618cea 100644 --- a/frontend/src/renderer/pages/visualization/VisualizationURIModal.tsx +++ b/frontend/src/renderer/pages/visualization/VisualizationURIModal.tsx @@ -547,6 +547,9 @@ export const VisualizationURIModal = ({ size="90%" centered data-testid="config-uri-selection-modal" + {...(window.env.E2E_TEST === 'true' && { + transitionProps: { duration: 0 }, + })} > { + const path = + (await window.api.fs.getZenodoDataPath()) + + '/iter_scenario_53298_seq1_DD3.nc'; + return path; + }); await ensureCssElementIsDisplayed('config-uri-selection-modal'); await writeTextInCssElement( 'config-uri-selection-modal-uri-text-input', - 'imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', + dataPath, + ); + const buttonSavingUris = await ensureCssElementIsDisplayed( + 'config-uri-selection-modal-add-uri-button', ); await findCssElementAndClickIt('config-uri-selection-modal-add-uri-button'); + await waitForElementToDisappear(buttonSavingUris); + await findCssElementAndClickIt( 'config-uri-selection-modal-validate-button', 100, 300, ); - await ensureCssElementIsDisplayed( - 'uriAccordion-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', - 200, - 100, - ); - await findCssElementAndClickIt( - 'uriAccordion-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', - 200, - 100, - ); + await findCssElementAndClickIt(`uriAccordion-${dataPath}`, 200, 100); await findCssElementAndClickIt( - 'folder-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/', + `folder-${dataPath}#core_profiles:0/`, 200, 100, ); await findCssElementAndClickIt( - 'folder-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/', + `folder-${dataPath}#core_profiles:0/profiles_1d[:]/`, 200, 100, ); await findCssElementAndClickIt( - 'folder-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/ion[:]/', + `folder-${dataPath}#core_profiles:0/profiles_1d[:]/ion[:]/`, 200, 100, ); @@ -125,7 +130,7 @@ describe('UI Tests for plotted data', function () { ); // Click on temperature checkbox to start a new plot await findCssElementAndClickIt( - 'checkbox-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/ion[:]/temperature', + `checkbox-${dataPath}#core_profiles:0/profiles_1d[:]/ion[:]/temperature`, ); // Check that there is one dataplot created await waitForValue( @@ -149,7 +154,7 @@ describe('UI Tests for plotted data', function () { ); // Click on density checkbox to plot a second axis await findCssElementAndClickIt( - 'checkbox-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/ion[:]/density', + `checkbox-${dataPath}#core_profiles:0/profiles_1d[:]/ion[:]/density`, ); // Check that the Y plot is defined await waitForValue( @@ -190,12 +195,20 @@ describe('UI Tests for plotted data', function () { ); /// - /// Add the URI 'imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106' to the configuration and navigate in the accordion node tree + /// Add the URI of iter_scenario_53298_seq1_DD3.nc to the configuration and navigate in the accordion node tree /// + const dataPath: string = await ( + await getDriver() + ).executeScript(async () => { + const path = + (await window.api.fs.getZenodoDataPath()) + + '/iter_scenario_53298_seq1_DD3.nc'; + return path; + }); await ensureCssElementIsDisplayed('config-uri-selection-modal'); await writeTextInCssElement( 'config-uri-selection-modal-uri-text-input', - 'imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', + dataPath, true, ); await findCssElementAndClickIt('config-uri-selection-modal-add-uri-button'); @@ -204,23 +217,15 @@ describe('UI Tests for plotted data', function () { 100, 300, ); - await ensureCssElementIsDisplayed( - 'uriAccordion-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', - 200, - 100, - ); + await ensureCssElementIsDisplayed(`uriAccordion-${dataPath}`, 200, 100); + await findCssElementAndClickIt(`uriAccordion-${dataPath}`, 200, 100); await findCssElementAndClickIt( - 'uriAccordion-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', + `folder-${dataPath}#core_profiles:0/`, 200, 100, ); await findCssElementAndClickIt( - 'folder-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/', - 200, - 100, - ); - await findCssElementAndClickIt( - 'folder-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/', + `folder-${dataPath}#core_profiles:0/profiles_1d[:]/`, 200, 100, ); @@ -235,7 +240,7 @@ describe('UI Tests for plotted data', function () { ); // Click on j_total checkbox to start a new plot await findCssElementAndClickIt( - 'checkbox-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/j_total', + `checkbox-${dataPath}#core_profiles:0/profiles_1d[:]/j_total`, ); // Check that there is one dataplot created await waitForValue( @@ -245,7 +250,7 @@ describe('UI Tests for plotted data', function () { ); // Click on j_ohmic checkbox to plot a second data await findCssElementAndClickIt( - 'checkbox-imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106#core_profiles:0/profiles_1d[:]/j_ohmic', + `checkbox-${dataPath}#core_profiles:0/profiles_1d[:]/j_ohmic`, ); // Check that there is two dataplot created await waitForValue( @@ -259,7 +264,7 @@ describe('UI Tests for plotted data', function () { await waitForValue( 'First y value of j_total at origin', async () => originalDataFromActive.dataPlot[0]?.plot[0]?.y[0], - -191505.77227601665, + -1018173.9490004762, (actual, expected) => actual === expected, ); await waitForValue( @@ -268,13 +273,13 @@ describe('UI Tests for plotted data', function () { originalDataFromActive.dataPlot[0]?.plot[0]?.y[ originalDataFromActive.dataPlot[0]?.plot[0]?.y.length - 1 ], - -12283.374007355182, + -379752.40595339175, (actual, expected) => actual === expected, ); await waitForValue( 'First y value of j_ohmic at origin', async () => originalDataFromActive.dataPlot[0]?.plot[1]?.y[0], - -155459.99347997818, + -942579.3029552045, (actual, expected) => actual === expected, ); await waitForValue( @@ -283,7 +288,7 @@ describe('UI Tests for plotted data', function () { originalDataFromActive.dataPlot[0]?.plot[1]?.y[ originalDataFromActive.dataPlot[0]?.plot[1]?.y.length - 1 ], - -14479.974999151873, + -34126.34158638138, (actual, expected) => actual === expected, ); @@ -293,12 +298,14 @@ describe('UI Tests for plotted data', function () { await writeTextInCssElement('data-range-min-input', '0.2', true); await writeTextInCssElement('data-range-max-input', '0.8', true); await findCssElementAndClickIt('data-range-apply-input'); + await new Promise((r) => setTimeout(r, 1500)); await findCssElementAndClickIt('customization-save-button'); + await new Promise((r) => setTimeout(r, 1500)); const activeAtFirstApplied = (await getTestState()).active; await waitForValue( 'First y value of j_total at first applied', async () => activeAtFirstApplied.dataPlot[0]?.plot[0]?.y[0], - -200577.796875, + -1409056.125, (actual, expected) => actual === expected, ); await waitForValue( @@ -307,13 +314,13 @@ describe('UI Tests for plotted data', function () { activeAtFirstApplied.dataPlot[0]?.plot[0]?.y[ activeAtFirstApplied.dataPlot[0]?.plot[0]?.y.length - 1 ], - -102633.421875, + -402749.40625, (actual, expected) => actual === expected, ); await waitForValue( 'First y value of j_ohmic at first applied', async () => activeAtFirstApplied.dataPlot[0]?.plot[1]?.y[0], - -158003.015625, + -1058855.625, (actual, expected) => actual === expected, ); await waitForValue( @@ -322,7 +329,7 @@ describe('UI Tests for plotted data', function () { activeAtFirstApplied.dataPlot[0]?.plot[1]?.y[ activeAtFirstApplied.dataPlot[0]?.plot[1]?.y.length - 1 ], - -95764.546875, + -274728.34375, (actual, expected) => actual === expected, ); @@ -332,12 +339,14 @@ describe('UI Tests for plotted data', function () { await writeTextInCssElement('data-range-min-input', '0.4', true); await writeTextInCssElement('data-range-max-input', '0.6', true); await findCssElementAndClickIt('data-range-apply-input'); + await new Promise((r) => setTimeout(r, 1500)); await findCssElementAndClickIt('customization-save-button'); + await new Promise((r) => setTimeout(r, 1500)); const activeAtSecondApplied = (await getTestState()).active; await waitForValue( 'First y value of j_total at second applied', async () => activeAtSecondApplied.dataPlot[0]?.plot[0]?.y[0], - -220957.3125, + -1159740.25, (actual, expected) => actual === expected, ); await waitForValue( @@ -346,13 +355,13 @@ describe('UI Tests for plotted data', function () { activeAtSecondApplied.dataPlot[0]?.plot[0]?.y[ activeAtSecondApplied.dataPlot[0]?.plot[0]?.y.length - 1 ], - -195015.078125, + -697106.25, (actual, expected) => actual === expected, ); await waitForValue( 'First y value of j_ohmic at second applied', async () => activeAtSecondApplied.dataPlot[0]?.plot[1]?.y[0], - -200219.234375, + -910444.625, (actual, expected) => actual === expected, ); await waitForValue( @@ -361,7 +370,7 @@ describe('UI Tests for plotted data', function () { activeAtSecondApplied.dataPlot[0]?.plot[1]?.y[ activeAtSecondApplied.dataPlot[0]?.plot[1]?.y.length - 1 ], - -182456.90625, + -506609.75, (actual, expected) => actual === expected, ); @@ -379,14 +388,14 @@ describe('UI Tests for plotted data', function () { ).getAttribute('data-loading'), null, (actual, expected) => actual === expected, - 10, ); await findCssElementAndClickIt('customization-save-button'); + await new Promise((r) => setTimeout(r, 1500)); const activeAtThirdApplied = (await getTestState()).active; await waitForValue( 'First y value of j_total at third applied', async () => activeAtThirdApplied.dataPlot[0]?.plot[0]?.y[0], - -200577.796875, + -1409056.125, (actual, expected) => actual === expected, ); await waitForValue( @@ -395,13 +404,13 @@ describe('UI Tests for plotted data', function () { activeAtThirdApplied.dataPlot[0]?.plot[0]?.y[ activeAtThirdApplied.dataPlot[0]?.plot[0]?.y.length - 1 ], - -102633.421875, + -402749.40625, (actual, expected) => actual === expected, ); await waitForValue( 'First y value of j_ohmic at third applied', async () => activeAtThirdApplied.dataPlot[0]?.plot[1]?.y[0], - -158003.015625, + -1058855.625, (actual, expected) => actual === expected, ); await waitForValue( @@ -410,7 +419,7 @@ describe('UI Tests for plotted data', function () { activeAtThirdApplied.dataPlot[0]?.plot[1]?.y[ activeAtThirdApplied.dataPlot[0]?.plot[1]?.y.length - 1 ], - -95764.546875, + -274728.34375, (actual, expected) => actual === expected, ); @@ -429,11 +438,12 @@ describe('UI Tests for plotted data', function () { 10, ); await findCssElementAndClickIt('customization-save-button'); + await new Promise((r) => setTimeout(r, 1500)); const restoredDataFromActive = (await getTestState()).active; await waitForValue( 'First y value of j_total at restoration', async () => restoredDataFromActive.dataPlot[0]?.plot[0]?.y[0], - -191505.765625, + -1018173.9375, (actual, expected) => actual === expected, ); await waitForValue( @@ -442,13 +452,13 @@ describe('UI Tests for plotted data', function () { restoredDataFromActive.dataPlot[0]?.plot[0]?.y[ restoredDataFromActive.dataPlot[0]?.plot[0]?.y.length - 1 ], - -12283.3740234375, + -379752.40625, (actual, expected) => actual === expected, ); await waitForValue( 'First y value of j_ohmic at restoration', async () => restoredDataFromActive.dataPlot[0]?.plot[1]?.y[0], - -155460, + -942579.3125, (actual, expected) => actual === expected, ); await waitForValue( @@ -457,7 +467,7 @@ describe('UI Tests for plotted data', function () { restoredDataFromActive.dataPlot[0]?.plot[1]?.y[ restoredDataFromActive.dataPlot[0]?.plot[1]?.y.length - 1 ], - -14479.974609375, + -34126.33984375, (actual, expected) => actual === expected, ); }); @@ -491,14 +501,22 @@ describe('UI Tests for plotted data', function () { ); /// - /// Add the URI 'imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106' to the configuration and navigate in the accordion node tree + /// Add the URI of iter_scenario_53298_seq1_DD3.nc to the configuration and navigate in the accordion node tree /// + const dataPath: string = await ( + await getDriver() + ).executeScript(async () => { + const path = + (await window.api.fs.getZenodoDataPath()) + + '/iter_scenario_53298_seq1_DD3.nc'; + return path; + }); const uriModal = await ensureCssElementIsDisplayed( 'config-uri-selection-modal', ); await writeTextInCssElement( 'config-uri-selection-modal-uri-text-input', - 'imas:hdf5?path=/work/imas/shared/imasdb/ITER/3/134173/106', + dataPath, true, ); await findCssElementAndClickIt('config-uri-selection-modal-add-uri-button'); @@ -614,6 +632,7 @@ describe('UI Tests for plotted data', function () { 'config-uri-selection-modal', ); await writeTextInCssElement( + // TODO : use dataPath 'config-uri-selection-modal-uri-text-input', 'imas:hdf5?user=imbeauf;pulse=58089;run=4;database=west;version=3', true, diff --git a/frontend/src/tests/setup.ts b/frontend/src/tests/setup.ts index 9f8b76db..3cd687aa 100644 --- a/frontend/src/tests/setup.ts +++ b/frontend/src/tests/setup.ts @@ -26,7 +26,7 @@ export async function startApp() { ...process.env, ELECTRON_ENABLE_LOGGING: 'true', ELECTRON_ENABLE_STACK_DUMPING: 'true', - // E2E_TEST: 'true', + E2E_TEST: process.env.E2E_TEST, }, }, ); diff --git a/frontend/src/tests/utils/testToolBox.ts b/frontend/src/tests/utils/testToolBox.ts index fbdacdc4..b75c86d2 100644 --- a/frontend/src/tests/utils/testToolBox.ts +++ b/frontend/src/tests/utils/testToolBox.ts @@ -9,14 +9,9 @@ export async function getCssElementFromDataTestId( const cssElement = await getDriver().wait( until.elementLocated(By.css(`[data-testid="${cssElementDataTestIdName}"]`)), timeout, + `Element "${cssElementDataTestIdName}" not found`, ); - - if (!cssElement) { - throw new Error( - `No CSS element found under the name ${cssElementDataTestIdName}, abort`, - ); - } - + await getDriver().wait(until.elementIsVisible(cssElement), timeout); return cssElement; } @@ -62,18 +57,12 @@ export async function ensureCssElementIsDisplayed( delayMs = 100, ): Promise { console.info('Ensuring element is displayed : ', cssElementDataTestIdName); - const cssElement = await getCssElementFromDataTestId( + const cssElement: WebElement = await getCssElementFromDataTestId( cssElementDataTestIdName, retries * delayMs, ); - for (let i = 0; i < retries; i++) { - await new Promise((res) => setTimeout(res, delayMs)); - - if (await cssElement.isDisplayed()) { - break; - } - } + await getDriver().wait(until.elementIsVisible(cssElement), retries * delayMs); expect( await cssElement.isDisplayed(), @@ -91,7 +80,7 @@ export async function writeTextInCssElement( if (clearText) { if ( (await input.getAttribute('value')) != undefined && - (await input.getAttribute('value')).length > 0 + (await input.getAttribute('value'))?.length > 0 ) { while ((await input.getAttribute('value')).length > 0) { await input.sendKeys(Key.BACK_SPACE); @@ -106,23 +95,16 @@ export async function findCssElementAndClickIt( retries = 100, delayMs = 100, ) { - const button = await ensureCssElementIsDisplayed( + const button = await getCssElementFromDataTestId( cssElementDataTestIdName, - retries, - delayMs, + retries * delayMs, ); - for (let i = 0; i < retries; i++) { - await new Promise((res) => setTimeout(res, delayMs)); - - if (await button.isEnabled()) { - break; - } - } + await getDriver().wait(until.elementIsEnabled(button), retries * delayMs); expect( - await button.isEnabled(), - `Button "${cssElementDataTestIdName}" was found but isEnabled() returned false.`, + await button.isDisplayed(), + `Button "${cssElementDataTestIdName}" was found but isDisplayed() returned false.`, ).to.be.true; await button.click(); } diff --git a/zenodo_datasets.txt b/zenodo_datasets.txt new file mode 100644 index 00000000..fe977fe8 --- /dev/null +++ b/zenodo_datasets.txt @@ -0,0 +1 @@ +https://zenodo.org/records/17062700/files/iter_scenario_53298_seq1_DD3.nc \ No newline at end of file From eac4e5cdfe01de1ace2ec7c5300a5cb00a83b523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Mon, 16 Mar 2026 11:55:00 +0100 Subject: [PATCH 12/13] temporarily remove error bars tests --- frontend/src/tests/plot-ui.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/tests/plot-ui.spec.ts b/frontend/src/tests/plot-ui.spec.ts index e8f7ad65..f6186fba 100644 --- a/frontend/src/tests/plot-ui.spec.ts +++ b/frontend/src/tests/plot-ui.spec.ts @@ -16,7 +16,7 @@ import { waitForValue, writeTextInCssElement, } from './utils'; -import { expect } from 'chai'; +// import { expect } from 'chai'; import '../config/bridge'; /** @@ -603,6 +603,7 @@ describe('UI Tests for plotted data', function () { ); }); + /* it('Should create a new configuration containing error bands, and plot error band data', async () => { /// /// Create a new configuration named 'New Plot Config' @@ -718,4 +719,5 @@ describe('UI Tests for plotted data', function () { expect(plotWithErrorBand.arrayminus.length > 0); } }); + */ }); From 18c617ba2ee59a4d31ac38bf9ca057d51020b5f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Fiaudrin?= Date: Wed, 18 Mar 2026 08:24:08 +0100 Subject: [PATCH 13/13] hide local folder for E2E tests --- .gitignore | 5 ++++- frontend/src/tests/visualization-ui.spec.ts | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 79f635bc..66674af6 100644 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,7 @@ saxon*.jar # ASV folder /.asv -.idea/ \ No newline at end of file +.idea/ + +# Local folder for E2E tests +e2e_datasets \ No newline at end of file diff --git a/frontend/src/tests/visualization-ui.spec.ts b/frontend/src/tests/visualization-ui.spec.ts index a78ee28b..458bb794 100644 --- a/frontend/src/tests/visualization-ui.spec.ts +++ b/frontend/src/tests/visualization-ui.spec.ts @@ -26,7 +26,7 @@ describe('UI Tests for Visualization Component', function () { afterEach(async () => { await setTestState({ configurations: [], active: null }); - // Ferme les modales restantes si besoin + // Close any remaining modals if necessary try { const modalOverlay = await driver.findElement( By.css('.mantine-Modal-overlay'), @@ -38,11 +38,11 @@ describe('UI Tests for Visualization Component', function () { ); await closeButton.click(); - // Attends la disparition de l'overlay + // Wait until the overlay disappears await driver.wait(until.stalenessOf(modalOverlay), 3000); } } catch { - // Ignore si la modale n'existe pas + // Ignore if the modal does not exist } });