diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml
index 8401cb82..bfbb19c3 100644
--- a/.github/workflows/deployment.yml
+++ b/.github/workflows/deployment.yml
@@ -150,4 +150,61 @@ jobs:
context: .
push: true
tags: ghcr.io/diracgrid/diracx-web/static:${{ needs.release-please.outputs.tag_name }}
- platforms: linux/amd64,linux/arm64
\ No newline at end of file
+ platforms: linux/amd64,linux/arm64
+
+ # Helm charts are updated in diracx-charts
+ # -----------------------------------------
+
+ update-charts:
+ name: Update Helm charts
+ needs:
+ - release-please
+ - build-deploy-diracx-web-release-image
+ runs-on: ubuntu-latest
+ if: ${{ needs.release-please.outputs.release_created == 'true' }}
+ steps:
+ - name: Checkout diracx-web (for update script)
+ uses: actions/checkout@v6
+ with:
+ path: diracx-web
+ sparse-checkout: .github/workflows
+
+ - name: Checkout diracx-charts
+ uses: actions/checkout@v6
+ with:
+ repository: DIRACGrid/diracx-charts
+ token: ${{ secrets.CHARTS_UPDATE_TOKEN }}
+ path: diracx-charts
+
+ - name: Configure Git
+ run: |
+ cd diracx-charts
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+
+ - name: Update chart versions
+ run: |
+ python diracx-web/.github/workflows/update_chart_version.py \
+ --charts-dir diracx-charts \
+ --web-version "${{ needs.release-please.outputs.tag_name }}"
+
+ - name: Install pre-commit
+ run: pip install pre-commit
+
+ - name: Run pre-commit to update README
+ run: |
+ cd diracx-charts
+ pre-commit run --all-files || true
+
+ - name: Commit and push changes
+ if: success()
+ run: |
+ cd diracx-charts
+ git add -A
+
+ if ! git diff --cached --quiet; then
+ git commit -m "chore: bump diracx-web to ${{ needs.release-please.outputs.tag_name }}"
+ git push origin master
+ else
+ echo "No changes to commit"
+ fi
diff --git a/.github/workflows/gubbins-test.yml b/.github/workflows/gubbins-test.yml
index 9ec0dfec..d247137f 100644
--- a/.github/workflows/gubbins-test.yml
+++ b/.github/workflows/gubbins-test.yml
@@ -54,101 +54,41 @@ jobs:
prepare-gubbins-backend:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'push' || github.repository == 'DIRACGrid/diracx-web' }}
- defaults:
- run:
- # We need extglob for REFERENCE_BRANCH substitution
- shell: bash -l -O extglob {0}
steps:
- name: Clone source
run: |
cd ..
git clone https://github.com/DIRACGrid/diracx.git
- # Prepare the gubbins extension
- - name: Where the magic happens (Move extensions to a temporary directory)
- run: |
- # We have to copy the code to another directory
- # and make it a git repository by itself because otherwise the
- # root in the pyproject to do not make sense once mounted
- # in the containers.
- cp -r ../diracx/extensions/gubbins /tmp/
- sed -i 's@../..@.@g' /tmp/gubbins/pyproject.toml
- sed -i 's@../../@@g' /tmp/gubbins/gubbins-*/pyproject.toml
-
- - name: Upload artifact
- uses: actions/upload-artifact@v7
- with:
- name: gubbins
- path: /tmp/gubbins
- include-hidden-files: true
-
- # Prepare the gubbins image
- # - Build the gubbins wheels
- - uses: actions/setup-python@v6
- with:
- python-version: '3.14'
- - name: Installing dependencies
- run: |
- cd ../diracx
- python -m pip install \
- build \
- python-dateutil \
- pytz \
- readme_renderer[md] \
- requests \
- setuptools_scm
- - name: Build distributions
- run: |
- cd ../diracx
- for pkg_dir in $PWD/diracx-*; do
- echo "Building $pkg_dir"
- python -m build --outdir $PWD/dist $pkg_dir
- done
- # Also build the diracx metapackage
- python -m build --outdir $PWD/dist .
- # And build the gubbins package
- for pkg_dir in $PWD/extensions/gubbins/gubbins-*; do
- # Skip the testing package
- if [[ "${pkg_dir}" =~ .*testing.* ]];
- then
- echo "Do not build ${pkg_dir}";
- continue;
- fi
- echo "Building $pkg_dir"
- python -m build --outdir $PWD/dist $pkg_dir
- done
- - name: "Find wheels"
- id: find_wheel
- run: |
- cd ../diracx/dist
- # We need to copy them there to be able to access them in the RUN --mount
- cp diracx*.whl gubbins*.whl ../extensions/containers/services/
- for wheel_fn in *.whl; do
- pkg_name=$(basename "${wheel_fn}" | cut -d '-' -f 1)
- echo "${pkg_name}-wheel-name=$(ls "${pkg_name}"-*.whl)" >> $GITHUB_OUTPUT
- done
-
- # - Build the gubbins image using the wheels
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v4
+ # Build gubbins container images using pixi
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- - name: Build and export service
+ - name: Build gubbins services image
uses: docker/build-push-action@v7
with:
- context: ../diracx/extensions/containers/services
- tags: gubbins/services:dev
+ context: ../diracx
+ file: ../diracx/containers/Dockerfile
+ build-args: |
+ PIXI_ENV=gubbins-container-services
+ tags: ghcr.io/gubbins/services:dev
outputs: type=docker,dest=/tmp/gubbins_services_image.tar
+
+ - name: Build gubbins client image
+ uses: docker/build-push-action@v7
+ with:
+ context: ../diracx
+ file: ../diracx/containers/Dockerfile
build-args: |
- EXTRA_PACKAGES_TO_INSTALL=git+https://github.com/DIRACGrid/DIRAC.git@integration
- EXTENSION_CUSTOM_SOURCES_TO_INSTALL=/bindmount/gubbins_db*.whl,/bindmount/gubbins_routers*.whl,/bindmount/gubbins_client*.whl
+ PIXI_ENV=gubbins-container-client
+ tags: ghcr.io/gubbins/client:dev
+ outputs: type=docker,dest=/tmp/gubbins_client_image.tar
- - name: Upload artifact
+ - name: Upload gubbins images
uses: actions/upload-artifact@v7
with:
- name: gubbins-services-img
- path: /tmp/gubbins_services_image.tar
+ name: gubbins-images
+ path: /tmp/gubbins_*_image.tar
prepare-gubbins-frontend-as-a-standalone:
runs-on: ubuntu-latest
@@ -226,7 +166,7 @@ jobs:
# WARNING: In your CI/CD pipeline, you should remove the following steps #
# ------------------------------------------------------------------------#
- name: Download gubbins-web
- uses: actions/download-artifact@v8
+ uses: actions/download-artifact@v7
with:
name: gubbins-web
path: /tmp/gubbins-web
@@ -258,43 +198,57 @@ jobs:
# ========================================================================#
# WARNING: In your CI/CD pipeline, you should remove the following steps #
# ------------------------------------------------------------------------#
- - name: Download gubbins
- uses: actions/download-artifact@v8
- with:
- name: gubbins
- path: /tmp/gubbins
-
- - name: Create a new git repository
- run: |
- git init /tmp/gubbins/
-
- - name: Clone diracx source
+ - name: Clone diracx source
run: |
cd ..
git clone https://github.com/DIRACGrid/diracx.git
- - name: Download gubbins:services image
- uses: actions/download-artifact@v8
+ - name: Download gubbins images
+ uses: actions/download-artifact@v7
with:
- name: gubbins-services-img
+ name: gubbins-images
path: /tmp/
- - name: Load docker image
- run: docker load --input /tmp/gubbins_services_image.tar
+ - name: Load gubbins images
+ run: |
+ docker load --input /tmp/gubbins_services_image.tar
+ rm -f /tmp/gubbins_services_image.tar
+ docker load --input /tmp/gubbins_client_image.tar
+ rm -f /tmp/gubbins_client_image.tar
- name: Download gubbins-web
- uses: actions/download-artifact@v8
+ uses: actions/download-artifact@v7
with:
name: gubbins-web
path: /tmp/gubbins-web
# ------------------------------------------------------------------------#
# ========================================================================#
-
+
# Runs the demo with the extension source code
- - name: Start demo
+ - name: Start demo
run: |
cd ..
- diracx-charts/run_demo.sh --exit-when-done --set-value developer.autoReload=false --ci-values ./diracx/extensions/gubbins_values.yaml --load-docker-image "gubbins/services:dev" diracx/ /tmp/gubbins/ /tmp/gubbins-web/
+
+ # Download helm/kubectl/kind first
+ diracx-charts/run_demo.sh --only-download-deps
+
+ # Copy gubbins-charts to a temporary location and build dependencies
+ cp -r ./diracx/extensions/gubbins-charts /tmp/
+ diracx-charts/.demo/helm dependency build /tmp/gubbins-charts
+ # Replace the downloaded subchart with the locally-cloned one to
+ # ensure we have the pixi-compatible ConfigMap entrypoint
+ rm -f /tmp/gubbins-charts/charts/diracx-*.tgz
+ diracx-charts/.demo/helm package diracx-charts/diracx -d /tmp/gubbins-charts/charts/
+
+ diracx-charts/run_demo.sh \
+ --exit-when-done \
+ --set-value diracx.developer.autoReload=false \
+ --extension-chart-path /tmp/gubbins-charts \
+ --ci-values ./diracx/extensions/gubbins_values.yaml \
+ --load-docker-image "ghcr.io/gubbins/services:dev" \
+ --load-docker-image "ghcr.io/gubbins/client:dev" \
+ --prune-loaded-images \
+ /tmp/gubbins-web/
- name: Debugging information
run: |
diff --git a/.github/workflows/update_chart_version.py b/.github/workflows/update_chart_version.py
new file mode 100644
index 00000000..7c663501
--- /dev/null
+++ b/.github/workflows/update_chart_version.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+"""Update diracx-charts with a new diracx-web version.
+
+Bumps the chart version and updates the web image tag in values.yaml.
+Does NOT modify appVersion (that tracks the diracx server version).
+"""
+
+from __future__ import annotations
+
+import argparse
+import re
+import sys
+from pathlib import Path
+
+
+def bump_version(current_version: str) -> str:
+ """Bump a version: increment alpha number if present, otherwise patch."""
+ match = re.match(
+ r"^(\d+\.\d+\.\d+)-alpha\.(\d+)$", current_version
+ )
+ if match:
+ base, alpha = match.group(1), int(match.group(2))
+ return f"{base}-alpha.{alpha + 1}"
+
+ match = re.match(r"^(\d+)\.(\d+)\.(\d+)$", current_version)
+ if match:
+ major, minor, patch = match.groups()
+ return f"{major}.{minor}.{int(patch) + 1}"
+
+ raise ValueError(f"Invalid version format: {current_version}")
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(
+ description="Update diracx-charts for a new diracx-web release"
+ )
+ parser.add_argument(
+ "--charts-dir",
+ type=Path,
+ required=True,
+ help="Path to the diracx-charts repository",
+ )
+ parser.add_argument(
+ "--web-version",
+ required=True,
+ help="New diracx-web version (e.g., v0.1.0)",
+ )
+ args = parser.parse_args()
+
+ chart_yaml = args.charts_dir / "diracx" / "Chart.yaml"
+ values_yaml = args.charts_dir / "diracx" / "values.yaml"
+
+ for path in (chart_yaml, values_yaml):
+ if not path.exists():
+ print(f"Error: {path} not found")
+ sys.exit(1)
+
+ # Read and bump chart version
+ chart_content = chart_yaml.read_text()
+ version_match = re.search(r'^version:\s*"?([^"\n]+)"?', chart_content, re.MULTILINE)
+ if not version_match:
+ print("Error: could not find version in Chart.yaml")
+ sys.exit(1)
+
+ current_chart_version = version_match.group(1)
+ new_chart_version = bump_version(current_chart_version)
+
+ chart_content = re.sub(
+ r'^version:\s*.*$',
+ f'version: "{new_chart_version}"',
+ chart_content,
+ flags=re.MULTILINE,
+ )
+ chart_yaml.write_text(chart_content)
+ print(f"Chart version: {current_chart_version} -> {new_chart_version}")
+
+ # Update web image tag in values.yaml
+ values_content = values_yaml.read_text()
+ values_content = re.sub(
+ r'(^ web:\s*\n tag:\s*).*$',
+ rf'\g<1>{args.web_version}',
+ values_content,
+ flags=re.MULTILINE,
+ )
+ values_yaml.write_text(values_content)
+ print(f"Web image tag: {args.web_version}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/packages/diracx-web/test/e2e/dashboard.cy.ts b/packages/diracx-web/test/e2e/dashboard.cy.ts
index 4777a884..2e88b95b 100644
--- a/packages/diracx-web/test/e2e/dashboard.cy.ts
+++ b/packages/diracx-web/test/e2e/dashboard.cy.ts
@@ -11,7 +11,7 @@ describe("DashboardDrawer", { retries: { runMode: 5, openMode: 3 } }, () => {
);
});
- cy.visit("/");
+ cy.visitApp();
});
it("should render the drawer", () => {
diff --git a/packages/diracx-web/test/e2e/importExportState.cy.ts b/packages/diracx-web/test/e2e/importExportState.cy.ts
index a7a1e041..e46fcf38 100644
--- a/packages/diracx-web/test/e2e/importExportState.cy.ts
+++ b/packages/diracx-web/test/e2e/importExportState.cy.ts
@@ -5,7 +5,7 @@ describe("Export and import app state", () => {
beforeEach(() => {
cy.login();
- cy.visit("/");
+ cy.visitApp();
// Open 2 Job Monitor apps
cy.get('[data-testid="add-application-button"]').click();
diff --git a/packages/diracx-web/test/e2e/jobMonitor.columns.cy.ts b/packages/diracx-web/test/e2e/jobMonitor.columns.cy.ts
index 7d2a6207..02b8cc72 100644
--- a/packages/diracx-web/test/e2e/jobMonitor.columns.cy.ts
+++ b/packages/diracx-web/test/e2e/jobMonitor.columns.cy.ts
@@ -9,9 +9,8 @@ import {
describe("Job Monitor - Columns", () => {
beforeEach(() => {
cy.login();
-
- cy.visit("/");
setupJobMonitorDashboard();
+ cy.visitApp();
cy.contains("Job Monitor").click();
diff --git a/packages/diracx-web/test/e2e/jobMonitor.filters.cy.ts b/packages/diracx-web/test/e2e/jobMonitor.filters.cy.ts
index 7af4344e..a49d7a8f 100644
--- a/packages/diracx-web/test/e2e/jobMonitor.filters.cy.ts
+++ b/packages/diracx-web/test/e2e/jobMonitor.filters.cy.ts
@@ -9,9 +9,8 @@ import {
describe("Job Monitor - Filters", () => {
beforeEach(() => {
cy.login();
-
- cy.visit("/");
setupJobMonitorDashboard();
+ cy.visitApp();
cy.contains("Job Monitor").click();
diff --git a/packages/diracx-web/test/e2e/jobMonitor.pagination.cy.ts b/packages/diracx-web/test/e2e/jobMonitor.pagination.cy.ts
index c3f96491..6451397d 100644
--- a/packages/diracx-web/test/e2e/jobMonitor.pagination.cy.ts
+++ b/packages/diracx-web/test/e2e/jobMonitor.pagination.cy.ts
@@ -9,9 +9,8 @@ import {
describe("Job Monitor - Pagination", () => {
beforeEach(() => {
cy.login();
-
- cy.visit("/");
setupJobMonitorDashboard();
+ cy.visitApp();
cy.contains("Job Monitor").click();
diff --git a/packages/diracx-web/test/e2e/jobMonitor.pieChart.cy.ts b/packages/diracx-web/test/e2e/jobMonitor.pieChart.cy.ts
index b3a18a1f..35bf548b 100644
--- a/packages/diracx-web/test/e2e/jobMonitor.pieChart.cy.ts
+++ b/packages/diracx-web/test/e2e/jobMonitor.pieChart.cy.ts
@@ -9,9 +9,8 @@ import {
describe("Job Monitor - Pie Chart", () => {
beforeEach(() => {
cy.login();
-
- cy.visit("/");
setupJobMonitorDashboard();
+ cy.visitApp();
cy.contains("Job Monitor").click();
diff --git a/packages/diracx-web/test/e2e/jobMonitor.rowActions.cy.ts b/packages/diracx-web/test/e2e/jobMonitor.rowActions.cy.ts
index 6869e6d1..b6266679 100644
--- a/packages/diracx-web/test/e2e/jobMonitor.rowActions.cy.ts
+++ b/packages/diracx-web/test/e2e/jobMonitor.rowActions.cy.ts
@@ -9,9 +9,8 @@ import {
describe("Job Monitor - Row Actions", () => {
beforeEach(() => {
cy.login();
-
- cy.visit("/");
setupJobMonitorDashboard();
+ cy.visitApp();
cy.contains("Job Monitor").click();
diff --git a/packages/diracx-web/test/e2e/jobMonitor.sandbox.cy.ts b/packages/diracx-web/test/e2e/jobMonitor.sandbox.cy.ts
index 25fc95e3..e663b3c5 100644
--- a/packages/diracx-web/test/e2e/jobMonitor.sandbox.cy.ts
+++ b/packages/diracx-web/test/e2e/jobMonitor.sandbox.cy.ts
@@ -9,13 +9,11 @@ import {
describe("Job Monitor - Sandbox Download", () => {
beforeEach(() => {
cy.login();
- cy.visit("/");
setupJobMonitorDashboard();
+ cy.visitApp();
cy.contains("Job Monitor").click();
// Wait for the table to be ready
- cy.contains("Loading OIDC Configuration").should("not.exist");
- cy.contains("Loading").should("not.exist");
cy.get('[data-testid="loading-skeleton"]').should("not.exist");
});
diff --git a/packages/diracx-web/test/e2e/loginOut.cy.ts b/packages/diracx-web/test/e2e/loginOut.cy.ts
index fa6d2512..5b6c04e9 100644
--- a/packages/diracx-web/test/e2e/loginOut.cy.ts
+++ b/packages/diracx-web/test/e2e/loginOut.cy.ts
@@ -1,4 +1,5 @@
///
+///
// Make sure the user can login and logout
describe("Login and Logout", () => {
@@ -7,14 +8,10 @@ describe("Login and Logout", () => {
// so we must tell it to visit our website with the `cy.visit()` command.
// Since we want to visit the same URL at the start of all our tests,
// we include it in our beforeEach function so that it runs before each test
- cy.visit("/");
+ cy.visit("/auth");
});
it("login", () => {
- // The user is redirected to the /auth page because not authenticated
- // Make sure we are on the /auth page
- cy.url().should("include", "/auth");
-
// Continue with the default parameters
cy.get('[data-testid="login-form-button"]').click();
@@ -57,16 +54,12 @@ describe("Login and Logout", () => {
// Logout
cy.contains("Logout").click();
- // The user is redirected back to the /auth page
- cy.url().should("include", "/auth");
-
- // The user is logged out
- // The login button should be present
- cy.get('[data-testid="login-form-button"]').should("exist");
-
- // The user tries to access the dashboard page without being connected
- // The user is redirected to the /auth page
- cy.visit("/");
- cy.url().should("include", "/auth");
+ // The user is logged out and redirected back to the /auth page
+ // Wait for the login button to reappear as the definitive signal.
+ // Uses a longer timeout because the logout → OIDC session clear →
+ // redirect → render chain can take longer than the default 4s in CI.
+ cy.get('[data-testid="login-form-button"]', { timeout: 10000 }).should(
+ "exist",
+ );
});
});
diff --git a/packages/diracx-web/test/e2e/support/commands.ts b/packages/diracx-web/test/e2e/support/commands.ts
index 2f440e0b..7751119c 100644
--- a/packages/diracx-web/test/e2e/support/commands.ts
+++ b/packages/diracx-web/test/e2e/support/commands.ts
@@ -2,7 +2,7 @@
Cypress.Commands.add("login", () => {
cy.session("login", () => {
- cy.visit("/");
+ cy.visit("/auth");
// Login
cy.get('[data-testid="login-form-button"]').click();
@@ -22,3 +22,11 @@ Cypress.Commands.add("login", () => {
cy.url().should("include", "/auth");
});
});
+
+Cypress.Commands.add("visitApp", () => {
+ cy.visit("/");
+ // Wait for the authenticated dashboard to be fully rendered.
+ // The permanent drawer is always present once OIDC initialization,
+ // session validation, and React rendering are all complete.
+ cy.get('[data-testid="drawer-permanent"]').should("exist");
+});
diff --git a/packages/diracx-web/test/e2e/support/index.d.ts b/packages/diracx-web/test/e2e/support/index.d.ts
index a76aa482..9ceeb992 100644
--- a/packages/diracx-web/test/e2e/support/index.d.ts
+++ b/packages/diracx-web/test/e2e/support/index.d.ts
@@ -7,5 +7,11 @@ declare namespace Cypress {
* Uses cy.session() so the login flow only runs once per spec.
*/
login(): Chainable;
+
+ /**
+ * Visit the app and wait for the OIDC provider to initialize.
+ * Use this instead of cy.visit("/") after cy.login().
+ */
+ visitApp(): Chainable;
}
}
diff --git a/packages/diracx-web/test/e2e/support/jobMonitorUtils.ts b/packages/diracx-web/test/e2e/support/jobMonitorUtils.ts
index f1369f8a..4194cba6 100644
--- a/packages/diracx-web/test/e2e/support/jobMonitorUtils.ts
+++ b/packages/diracx-web/test/e2e/support/jobMonitorUtils.ts
@@ -1,6 +1,6 @@
/**
* Set up the dashboard with two Job Monitor apps via sessionStorage.
- * Call this after cy.login() and before cy.visit("/").
+ * Call this after cy.login() and before cy.visitApp().
*/
export function setupJobMonitorDashboard() {
cy.window().then((win) => {
@@ -150,8 +150,6 @@ export function addJobWithOutputSandbox() {
* If not, add jobs and refresh. Call after the table is visible.
*/
export function ensureMinimumJobs(minNumberOfJobs: number) {
- cy.contains("Loading OIDC Configuration").should("not.exist");
- cy.contains("Loading").should("not.exist");
cy.get('[data-testid="loading-skeleton"]').should("not.exist");
cy.get("body").then(($body) => {
diff --git a/packages/extensions/cypress.config.ts b/packages/extensions/cypress.config.ts
index 1f7c7b0d..f5c80839 100644
--- a/packages/extensions/cypress.config.ts
+++ b/packages/extensions/cypress.config.ts
@@ -3,8 +3,8 @@ import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
specPattern: "test/e2e/**/*.cy.ts",
- supportFile: false,
- setupNodeEvents(on, config) {
+ supportFile: "test/e2e/support/e2e.ts",
+ setupNodeEvents(_on, _config) {
// implement node event listeners here
},
},
diff --git a/packages/extensions/test/e2e/loginOut.cy.ts b/packages/extensions/test/e2e/loginOut.cy.ts
index 8f75aef1..a757a5f7 100644
--- a/packages/extensions/test/e2e/loginOut.cy.ts
+++ b/packages/extensions/test/e2e/loginOut.cy.ts
@@ -1,4 +1,5 @@
///
+///
// Make sure the user can login and logout
describe("Login and Logout", () => {
@@ -11,10 +12,6 @@ describe("Login and Logout", () => {
});
it("login", () => {
- // The user is redirected to the /auth page because not authenticated
- // Make sure we are on the /auth page
- cy.url().should("include", "/auth");
-
// Continue with the default parameters
cy.get('[data-testid="login-form-button"]').click();
@@ -41,11 +38,9 @@ describe("Login and Logout", () => {
cy.url().should("include", "/auth");
// From now on the user is logged in
- // The login buttton should not be present anymore
+ // The login button should not be present anymore
cy.get('[data-testid="login-form-button"]').should("not.exist");
-
- cy.visit("/");
- cy.contains("Owners").should("exist");
+ cy.contains("Owners", { timeout: 10000 }).should("exist");
// Click on the user avatar
cy.get(".MuiAvatar-root").click();
@@ -59,16 +54,12 @@ describe("Login and Logout", () => {
// Logout
cy.contains("Logout").click();
- // The user is redirected back to the /auth page
- cy.url().should("include", "/auth");
-
- // The user is logged out
- // The login button should be present
- cy.get('[data-testid="login-form-button"]').should("exist");
-
- // The user tries to access the dashboard page without being connected
- // The user is redirected to the /auth page
- cy.visit("/");
- cy.url().should("include", "/auth");
+ // The user is logged out and redirected back to the /auth page
+ // Wait for the login button to reappear as the definitive signal.
+ // Uses a longer timeout because the logout → OIDC session clear →
+ // redirect → render chain can take longer than the default 4s in CI.
+ cy.get('[data-testid="login-form-button"]', { timeout: 10000 }).should(
+ "exist",
+ );
});
});
diff --git a/packages/extensions/test/e2e/ownerMonitor.cy.ts b/packages/extensions/test/e2e/ownerMonitor.cy.ts
index 19e7bb10..33f0b0ed 100644
--- a/packages/extensions/test/e2e/ownerMonitor.cy.ts
+++ b/packages/extensions/test/e2e/ownerMonitor.cy.ts
@@ -1,22 +1,11 @@
///
+///
describe("Owner Monitor", () => {
beforeEach(() => {
- cy.session("login", () => {
- cy.visit("/auth");
- //login
- cy.get('[data-testid="login-form-button"]').click();
- cy.get("#login").type("admin@example.com");
- cy.get("#password").type("password");
+ cy.login();
- // Find the login button and click on it
- cy.get("button").click();
- // Grant access
- cy.get(":nth-child(1) > form > .dex-btn").click();
- cy.url().should("include", "/auth");
- });
-
- // Visit the page where the Job Monitor is rendered
+ // Visit the page where the Owner Monitor is rendered
cy.window().then((win) => {
win.sessionStorage.setItem(
"savedDashboard",
@@ -24,7 +13,7 @@ describe("Owner Monitor", () => {
);
});
- cy.visit("/");
+ cy.visitApp();
});
it("should render the drawer", () => {
diff --git a/packages/extensions/test/e2e/support/commands.ts b/packages/extensions/test/e2e/support/commands.ts
new file mode 100644
index 00000000..7751119c
--- /dev/null
+++ b/packages/extensions/test/e2e/support/commands.ts
@@ -0,0 +1,32 @@
+///
+
+Cypress.Commands.add("login", () => {
+ cy.session("login", () => {
+ cy.visit("/auth");
+
+ // Login
+ cy.get('[data-testid="login-form-button"]').click();
+
+ // Handle OIDC provider login (cross-origin)
+ const domain = Cypress.config()
+ .baseUrl?.replace("https://", "")
+ .split(":")[0];
+
+ cy.origin(`http://${domain}:32002`, () => {
+ cy.get("#login").type("admin@example.com");
+ cy.get("#password").type("password");
+ cy.get("button").click();
+ cy.get(":nth-child(1) > form > .dex-btn").click();
+ });
+
+ cy.url().should("include", "/auth");
+ });
+});
+
+Cypress.Commands.add("visitApp", () => {
+ cy.visit("/");
+ // Wait for the authenticated dashboard to be fully rendered.
+ // The permanent drawer is always present once OIDC initialization,
+ // session validation, and React rendering are all complete.
+ cy.get('[data-testid="drawer-permanent"]').should("exist");
+});
diff --git a/packages/extensions/test/e2e/support/e2e.ts b/packages/extensions/test/e2e/support/e2e.ts
new file mode 100644
index 00000000..f887c29a
--- /dev/null
+++ b/packages/extensions/test/e2e/support/e2e.ts
@@ -0,0 +1 @@
+import "./commands";
diff --git a/packages/extensions/test/e2e/support/index.d.ts b/packages/extensions/test/e2e/support/index.d.ts
new file mode 100644
index 00000000..9ceeb992
--- /dev/null
+++ b/packages/extensions/test/e2e/support/index.d.ts
@@ -0,0 +1,17 @@
+///
+
+declare namespace Cypress {
+ interface Chainable {
+ /**
+ * Log in via the OIDC provider and cache the session.
+ * Uses cy.session() so the login flow only runs once per spec.
+ */
+ login(): Chainable;
+
+ /**
+ * Visit the app and wait for the OIDC provider to initialize.
+ * Use this instead of cy.visit("/") after cy.login().
+ */
+ visitApp(): Chainable;
+ }
+}
diff --git a/renovate.json b/renovate.json
index 4fdcd402..d70fb4bc 100644
--- a/renovate.json
+++ b/renovate.json
@@ -1,6 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "extends": ["config:recommended"],
+ "extends": ["config:recommended", "helpers:pinGitHubActionDigests"],
"minimumReleaseAge": "7 days",
"packageRules": [
{