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 2d734dc9842 UI: Deflake backfill e2e tests on WebKit (#74200)
2d734dc9842 is described below

commit 2d734dc9842eafa1b92a20ba2c850bce2b012f5f
Author: Shahar Epstein <[email protected]>
AuthorDate: Mon Oct 5 18:00:31 2026 +0300

    UI: Deflake backfill e2e tests on WebKit (#74200)
    
    Several tests in backfill.spec.ts fail on WebKit in CI.
    
    The reprocess-behavior tests polled only for the "From" and "To" headers
    before snapshotting the column map. The Backfills page translates its
    headers from the "common" and "components" i18next namespaces, which
    load in parallel, and with useSuspense disabled the "Reprocess Behavior"
    header renders its raw key until its namespace arrives. A snapshot taken
    in that window made the detail lookup throw.
    
    The column-filter test reopens the menu after closing it with Escape.
    The closed menu stays mounted and visible until its exit animation ends,
    which a loaded WebKit delays for many seconds, so the page object took
    the stale menu for an open one and never re-clicked the trigger. Escape
    also returns keyboard focus to the trigger, which opens its tooltip over
    the stale first item and intercepts the probe. Only a menu whose state is
    open now counts, so the loop re-clicks the trigger, which closes the
    tooltip and reopens the menu.
---
 .../src/airflow/ui/tests/e2e/pages/BackfillPage.ts | 26 +++++++++++++++++-----
 1 file changed, 21 insertions(+), 5 deletions(-)

diff --git a/airflow-core/src/airflow/ui/tests/e2e/pages/BackfillPage.ts 
b/airflow-core/src/airflow/ui/tests/e2e/pages/BackfillPage.ts
index 53a3e6b107b..8c5e8cc4c4f 100644
--- a/airflow-core/src/airflow/ui/tests/e2e/pages/BackfillPage.ts
+++ b/airflow-core/src/airflow/ui/tests/e2e/pages/BackfillPage.ts
@@ -77,6 +77,9 @@ function getColumnIndex(columnMap: Map<string, number>, name: 
string): number {
   return index;
 }
 
+// Columns getBackfillDetailsByDateRange reads; findBackfillRowByDateRange 
waits for all of them.
+const DETAIL_COLUMNS = ["From", "To", "Reprocess Behavior", "Created at", 
"Completed at"];
+
 export class BackfillPage extends BasePage {
   public readonly backfillDateError: Locator;
   public readonly backfillFromTrigger: Locator;
@@ -283,7 +286,14 @@ export class BackfillPage extends BasePage {
             const fromIndex = columnMap.get("From");
             const toIndex = columnMap.get("To");
 
-            if (fromIndex === undefined || toIndex === undefined) {
+            // Headers come from the "common" and "components" i18next 
namespaces, which load in
+            // parallel. With useSuspense: false a header renders its raw key 
(e.g.
+            // `backfill.reprocessBehavior`) until its namespace arrives.
+            if (
+              fromIndex === undefined ||
+              toIndex === undefined ||
+              !DETAIL_COLUMNS.every((column) => columnMap.has(column))
+            ) {
               return false;
             }
 
@@ -316,7 +326,7 @@ export class BackfillPage extends BasePage {
         },
         {
           intervals: [2000, 5000],
-          message: `Backfill row with dates ${expectedFrom} ~ ${expectedTo} 
not found in table`,
+          message: `Backfill row with dates ${expectedFrom} ~ ${expectedTo} 
not found (waiting for columns: ${DETAIL_COLUMNS.join(", ")})`,
           timeout,
         },
       )
@@ -385,12 +395,18 @@ export class BackfillPage extends BasePage {
   }
 
   public async openFilterMenu(): Promise<void> {
+    // After Escape the menu content stays mounted and visible with 
data-state="closed" until its exit
+    // animation ends, which a loaded WebKit can delay for many seconds. Focus 
returns to the trigger
+    // meanwhile, so its tooltip opens over the stale first item. Only an open 
menu counts, so the loop
+    // re-clicks the trigger (which also closes the tooltip) instead of 
probing the stale content.
+    const openMenu = this.page.locator('[role="menu"][data-state="open"]');
+
     await expect(async () => {
-      if (!(await this.page.getByRole("menu").isVisible())) {
+      if (!(await openMenu.isVisible())) {
         await this.getFilterButton().click();
       }
-      await expect(this.page.getByRole("menu")).toBeVisible({ timeout: 3000 });
-      await this.page.getByRole("menuitem").first().click({ timeout: 3000, 
trial: true });
+      await expect(openMenu).toBeVisible({ timeout: 3000 });
+      await openMenu.getByRole("menuitem").first().click({ timeout: 3000, 
trial: true });
     }).toPass({ intervals: [1000], timeout: 15_000 });
   }
 

Reply via email to