Repository: apache/airflow
File: airflow-core/src/airflow/ui/tests/e2e/pages/BackfillPage.ts (TypeScript)

Code before:
    const behaviorLabels: Record<ReprocessBehavior, string> = {
      "All Runs": "All Runs",
      "Missing and Errored Runs": "Missing and Errored Runs",
      "Missing Runs": "Missing Runs",
    };

    const label = behaviorLabels[behavior];
    const radioItem = this.page.locator(`label:has-text("${label}")`).first();

    await radioItem.waitFor({ state: "visible", timeout: 5000 });
    await radioItem.click();
  }

  public async toggleColumn(columnName: string): Promise<void> {
    const menuItem = this.page.locator(`[role="menuitem"]:has-text("${columnName}")`);

    await menuItem.click();
  }

  public async waitForNoActiveBackfill(timeout: number = 300_000): Promise<void> {
    const { page } = this;
    const backfillInProgress = page.locator('text="Backfill in progress:"');

    await expect(async () => {
      await page.reload();
      await page.waitForLoadState("networkidle", { timeout: 30_000 });

      const isVisible = await backfillInProgress.isVisible();

      if (isVisible) {
        throw new Error("Backfill still in progress");
      }
    }).toPass({ intervals: [5000, 10_000, 15_000], timeout });
  }

  public async waitForTableDataLoaded(): Promise<void> {
    const firstCell = this.page.locator("table tbody tr:first-child td:first-child");

    await expect(firstCell).toBeVisible({ timeout: 30_000 });
    await expect(async () => {
      const text = await firstCell.textContent();

      if (text === null || text.trim() === "") {
        throw new Error("Table data still loading");
      }
    }).toPass({ timeout: 30_000 });
  }
}


Review comment (suggestion):
Is `networkidle` necessary here?

As far as I know, `networkidle` is generally discouraged since it’s timing-based and can be flaky, so it might be worth being cautious about using it.

Code after:
  }

  public async isBackfillPaused(): Promise<boolean> {
    await expect(this.pauseButton).toBeVisible({ timeout: 10_000 });
    const ariaLabel = await this.pauseButton.getAttribute("aria-label");

    return Boolean(ariaLabel?.toLowerCase().includes("unpause"));
  }

  public async navigateToBackfillsTab(dagName: string): Promise<void> {
    await this.navigateTo(BackfillPage.getBackfillsUrl(dagName));
    await expect(this.backfillsTable).toBeVisible({ timeout: 20_000 });
  }

  public async navigateToDagDetail(dagName: string): Promise<void> {
    await this.navigateTo(BackfillPage.getDagDetailUrl(dagName));
    await expect(this.triggerButton).toBeVisible({ timeout: 30_000 });
  }

  public async openBackfillDialog(): Promise<void> {
    await expect(this.triggerButton).toBeVisible({ timeout: 20_000 });
    await this.triggerButton.click();

    await expect(this.backfillModeRadio).toBeVisible({ timeout: 20_000 });
    await this.backfillModeRadio.click();

    await expect(this.backfillFromDateInput).toBeVisible({ timeout: 20_000 });
  }

  public async openFilterMenu(): Promise<void> {
    const filterButton = this.getFilterButton();

    await filterButton.click();

    const filterMenu = this.page.locator('[role="menu"]');

    await filterMenu.waitFor({ state: "visible", timeout: 5000 });
  }

  public async selectReprocessBehavior(behavior: ReprocessBehavior): Promise<void> {
    const behaviorLabels: Record<ReprocessBehavior, string> = {
      "All Runs": "All Runs",
      "Missing and Errored Runs": "Missing and Errored Runs",
      "Missing Runs": "Missing Runs",
    };

    const label = behaviorLabels[behavior];
    const radioItem = this.page.locator(`label:has-text("${label}")`).first();

    await radioItem.waitFor({ state: "visible", timeout: 5000 });
    await radioItem.click();