From cd5c6a0d182965efd93f9752bf97ca0420ac4824 Mon Sep 17 00:00:00 2001 From: Yuseok Jo Date: Mon, 19 Oct 2026 00:03:11 +0900 Subject: [PATCH 1/2] UI: Add regression tests for Dags list rows remounting on refresh --- .../ui/src/pages/DagsList/DagsList.test.tsx | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx index 68da78a137a44..6ffcdd63d71e6 100644 --- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx +++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx @@ -19,7 +19,7 @@ import "@testing-library/jest-dom/vitest"; import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import { delay, http, HttpResponse } from "msw"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { DagSchedulingState } from "openapi/requests/types.gen"; @@ -417,3 +417,81 @@ describe("Dags display toggle", () => { expect(screen.queryByTestId("table-list")).toBeNull(); }); }); + +const runningDag = { + ...successDag, + has_unfinished_runs: true, + latest_dag_runs: successDag.latest_dag_runs.map((run) => ({ ...run, end_date: null, state: "running" })), +}; + +// A running Dag keeps the list polling, and its run state counts change on every poll like they do +// while runs progress, so each refresh hands the list new data that must not remount its rows. +const serveRefreshingDagsList = () => { + let countsResponses = 0; + + server.use( + http.get("/ui/config", () => HttpResponse.json({ auto_refresh_interval: 1, page_size: 15 })), + http.get("/ui/dags", () => HttpResponse.json({ dags: [runningDag], total_entries: 1 })), + http.get("/ui/dags/run_state_counts", () => { + countsResponses += 1; + + return HttpResponse.json({ + dags: [{ dag_id: runningDag.dag_id, state_counts: { running: countsResponses } }], + state_count_limit: 1000, + }); + }), + http.get("/ui/dags/recent_task_instance_state_counts", () => HttpResponse.json({ dags: [] })), + ); + + return async () => { + const next = String(countsResponses + 1); + + await waitFor( + () => + expect(screen.getByTestId(`run-state-count-running-${runningDag.dag_id}`)).toHaveTextContent(next), + { timeout: 3000 }, + ); + }; +}; + +describe("Dags list auto-refresh", () => { + // happy-dom never reports an intersection, which would keep card content hidden for good. + beforeEach(() => { + vi.stubGlobal("IntersectionObserver", undefined); + }); + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it.each(["card", "table"])("keeps the %s rows mounted across a refresh", async (display) => { + const waitForRefresh = serveRefreshingDagsList(); + + localStorage.setItem(DAGS_LIST_DISPLAY_KEY, JSON.stringify(display)); + render(); + + await waitForRefresh(); + const toggle = screen.getByTestId("toggle-pause"); + + await waitForRefresh(); + + expect(screen.getByTestId("toggle-pause")).toBe(toggle); + }); + + it.each(["card", "table"])( + "keeps the pause dialog open in the %s view across a refresh", + async (display) => { + const waitForRefresh = serveRefreshingDagsList(); + + localStorage.setItem(DAGS_LIST_DISPLAY_KEY, JSON.stringify(display)); + render(); + + await waitForRefresh(); + fireEvent.click(screen.getByTestId("toggle-pause")); + const drainButton = await screen.findByTestId("drain-dag"); + + await waitForRefresh(); + + expect(screen.getByTestId("drain-dag")).toBe(drainButton); + }, + ); +}); From 1da1f962651059d7c3d8344f7aceff41944ceba4 Mon Sep 17 00:00:00 2001 From: Yuseok Jo Date: Mon, 19 Oct 2026 00:21:35 +0900 Subject: [PATCH 2/2] Drop the unused page_size from the Dags list refresh test config --- .../src/airflow/ui/src/pages/DagsList/DagsList.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx index 6ffcdd63d71e6..96c86fd352437 100644 --- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx +++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.test.tsx @@ -430,7 +430,7 @@ const serveRefreshingDagsList = () => { let countsResponses = 0; server.use( - http.get("/ui/config", () => HttpResponse.json({ auto_refresh_interval: 1, page_size: 15 })), + http.get("/ui/config", () => HttpResponse.json({ auto_refresh_interval: 1 })), http.get("/ui/dags", () => HttpResponse.json({ dags: [runningDag], total_entries: 1 })), http.get("/ui/dags/run_state_counts", () => { countsResponses += 1;