This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 15c8d68f84d Make Dags list e2e filters wait on the request that
actually fires (#72040)
15c8d68f84d is described below
commit 15c8d68f84d11b74e16aec5ea1f7d7e82c0e283c
Author: rjgoyln <[email protected]>
AuthorDate: Tue Sep 1 00:35:42 2026 +0800
Make Dags list e2e filters wait on the request that actually fires (#72040)
The Dags list is served by /ui/dags, but the status-filter and clear-search
helpers waited on /api/v2/dags and swallowed the timeout that followed, so
every
call spent its full budget waiting for a response that could never arrive
and
synchronised nothing. Clearing the search has no request to wait for at
all: it
returns to a query react-query still holds, so the list comes back from
cache.
Choosing which run-state control to click from the pill's visibility read an
element that leaves the DOM while the filter is being edited, so the answer
depended on where the click landed relative to that transition.
---
.../src/airflow/ui/tests/e2e/pages/DagsPage.ts | 50 +++++++++++-----------
1 file changed, 26 insertions(+), 24 deletions(-)
diff --git a/airflow-core/src/airflow/ui/tests/e2e/pages/DagsPage.ts
b/airflow-core/src/airflow/ui/tests/e2e/pages/DagsPage.ts
index 397d3a9b2fc..e0e66b9f44b 100644
--- a/airflow-core/src/airflow/ui/tests/e2e/pages/DagsPage.ts
+++ b/airflow-core/src/airflow/ui/tests/e2e/pages/DagsPage.ts
@@ -87,21 +87,13 @@ export class DagsPage extends BasePage {
* Clear the search input and wait for list to reset
*/
public async clearSearch(): Promise<void> {
- const responsePromise = this.page
- .waitForResponse((resp: Response) => resp.url().includes("/api/v2/dags")
&& resp.status() === 200, {
- timeout: 10_000,
- })
- .catch((error: unknown) => {
- if (error instanceof Error && !error.message.includes("Timeout")) {
- throw error;
- }
- });
-
// Click the clear button instead of programmatically clearing the input.
// The SearchBar component uses a 200ms debounce on keystroke changes,
// but the clear button calls onChange("") directly, bypassing the
debounce.
await this.page.getByTestId("clear-search").click();
- await responsePromise;
+ // Clearing goes back to a query the page already holds, which react-query
answers from cache
+ // instead of refetching, so the dropped search param is the only signal
there is.
+ await expect(this.page).not.toHaveURL(/name_pattern=/u);
await this.waitForDagList();
}
@@ -119,23 +111,33 @@ export class DagsPage extends BasePage {
public async filterByStatus(
status: "failed" | "needs_review" | "queued" | "running" | "success",
): Promise<void> {
- // Set up response listener before the click so we don't miss a fast
response.
- const responsePromise = this.page
- .waitForResponse((resp: Response) => resp.url().includes("/api/v2/dags")
&& resp.status() === 200, {
- timeout: 10_000,
- })
- .catch((error: unknown) => {
- if (error instanceof Error && !error.message.includes("Timeout")) {
- throw error;
- }
- });
+ // Set up response listener before the click so we don't miss a fast
response. /ui/dags shares
+ // its prefix with the page's other endpoints and is refetched on a poll
interval, so only the
+ // filters a request carries tell this fetch from one that predates it.
+ const responsePromise = this.page.waitForResponse(
+ (response: Response) => {
+ const url = new URL(response.url());
+ const filters = url.searchParams;
+
+ return (
+ url.pathname.endsWith("/ui/dags") &&
+ (status === "needs_review"
+ ? filters.get("has_pending_actions") === "true"
+ : filters.get("last_dag_run_state") === status) &&
+ response.status() === 200
+ );
+ },
+ { timeout: 10_000 },
+ );
if (status === "needs_review") {
// A boolean filter is active the moment it is picked from the menu.
await this.openAddFilterMenu();
await this.needsReviewFilter.click();
} else {
- if (await this.lastRunStatePill.isVisible().catch(() => false)) {
+ // A pill is only in the DOM once it collapses out of edit mode, so
sampling its
+ // visibility races that. The URL carries the same fact and never
animates.
+ if (new URL(this.page.url()).searchParams.has("last_dag_run_state")) {
// An existing pill already holds a value, so re-opening it leaves the
menu shut.
await this.lastRunStatePill.click();
await this.lastRunStateFilter.click();
@@ -145,8 +147,8 @@ export class DagsPage extends BasePage {
await this.page.getByTestId("add-filter-last_dag_run_state").click();
}
await
this.page.getByTestId(`last_dag_run_state-filter-${status}`).click();
- // Selecting blurs the pill, which collapses it ~150ms later. Wait for
that so a
- // follow-up call sees the pill rather than racing it and reopening the
menu.
+ // Selecting blurs the pill, which collapses it ~150ms later. Hand back
a settled bar
+ // instead of one mid-transition.
await expect(this.lastRunStatePill).toBeVisible({ timeout: 5000 });
}
await responsePromise;