vatsrahul1001 commented on code in PR #59633:
URL: https://github.com/apache/airflow/pull/59633#discussion_r2649687275


##########
airflow-core/src/airflow/ui/tests/e2e/specs/backfill.spec.ts:
##########
@@ -0,0 +1,111 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { expect, test } from "@playwright/test";
+import { BackfillPage } from "tests/e2e/pages/BackfillPage";
+
+/**
+ * Backfill E2E Tests
+ */
+
+const getPastDate = (daysAgo: number): string => {
+  const date = new Date();
+
+  date.setDate(date.getDate() - daysAgo);
+
+  return date.toISOString().slice(0, 16);
+};
+
+test.describe("Backfill Tabs", () => {

Review Comment:
   Tests are about creating backfills, not about "tabs". `Create Backfill` can 
be a better name



##########
airflow-core/src/airflow/ui/tests/e2e/specs/backfill.spec.ts:
##########
@@ -0,0 +1,111 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { expect, test } from "@playwright/test";
+import { BackfillPage } from "tests/e2e/pages/BackfillPage";
+
+/**
+ * Backfill E2E Tests
+ */
+
+const getPastDate = (daysAgo: number): string => {
+  const date = new Date();
+
+  date.setDate(date.getDate() - daysAgo);
+
+  return date.toISOString().slice(0, 16);
+};
+
+test.describe("Backfill Tabs", () => {
+  let backfillPage: BackfillPage;
+
+  const allRunsDagId = "example_nested_branch_dag";

Review Comment:
   We should get these names from testconfig



##########
airflow-core/src/airflow/ui/tests/e2e/pages/BackfillPage.ts:
##########
@@ -0,0 +1,157 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { expect } from "@playwright/test";
+import type { Locator, Page } from "@playwright/test";
+import { BasePage } from "tests/e2e/pages/BasePage";
+
+export type ReprocessBehavior = "All Runs" | "Missing and Errored Runs" | 
"Missing Runs";
+
+export type CreateBackfillOptions = {
+  fromDate: string;
+  reprocessBehavior: ReprocessBehavior;
+  toDate: string;
+};
+
+export type VerifyBackfillOptions = {
+  dagName: string;
+  expectedFromDate: string;
+  expectedToDate: string;
+  reprocessBehavior: ReprocessBehavior;
+};
+
+export class BackfillPage extends BasePage {
+  public readonly backfillDateError: Locator;
+  public readonly backfillFromDateInput: Locator;
+  public readonly backfillModeRadio: Locator;
+  public readonly backfillRunButton: Locator;
+  public readonly backfillsTable: Locator;
+  public readonly backfillToDateInput: Locator;
+  public readonly triggerButton: Locator;
+
+  public constructor(page: Page) {
+    super(page);
+    this.triggerButton = page.locator('button[aria-label="Trigger 
Dag"]:has-text("Trigger")');
+    this.backfillModeRadio = page.locator('label:has-text("Backfill")');
+    this.backfillFromDateInput = 
page.locator('input[type="datetime-local"]').first();
+    this.backfillToDateInput = 
page.locator('input[type="datetime-local"]').nth(1);
+    this.backfillRunButton = page.locator('button:has-text("Run Backfill")');
+    this.backfillsTable = page.locator("table");
+    this.backfillDateError = page.locator('text="Start Date must be before the 
End Date"');
+  }
+
+  public static getBackfillsUrl(dagName: string): string {
+    return `/dags/${dagName}/backfills`;
+  }
+
+  public static getDagDetailUrl(dagName: string): string {
+    return `/dags/${dagName}`;
+  }
+
+  public async createBackfill(dagName: string, options: 
CreateBackfillOptions): Promise<void> {
+    const { fromDate, reprocessBehavior, toDate } = options;
+
+    await this.navigateToDagDetail(dagName);
+    await this.openBackfillDialog();
+
+    await this.backfillFromDateInput.fill(fromDate);
+    await this.backfillToDateInput.fill(toDate);
+
+    await this.selectReprocessBehavior(reprocessBehavior);
+
+    const runsMessage = this.page.locator("text=/\\d+ runs? will be 
triggered|No runs matching/");
+
+    await expect(runsMessage).toBeVisible({ timeout: 10_000 });
+
+    await expect(this.backfillRunButton).toBeEnabled({ timeout: 5000 });
+    await this.backfillRunButton.click();
+
+    const backfillStatus = 
this.page.locator('[data-testid="backfill-status"]');
+
+    await expect(backfillStatus).toBeVisible({ timeout: 10_000 });
+  }
+
+  public async getBackfillsTableRows(): Promise<number> {
+    const rows = this.page.locator("table tbody tr");
+
+    await rows.first().waitFor({ state: "visible", timeout: 10_000 });
+    const count = await rows.count();
+
+    return count;
+  }
+
+  public async isBackfillDateErrorVisible(): Promise<boolean> {

Review Comment:
   This method isn't even used in the tests (the test uses 
expect(backfillDateError).toBeVisible() directly, which is correct). Consider 
removing unused methods.



##########
airflow-core/src/airflow/ui/tests/e2e/pages/BackfillPage.ts:
##########
@@ -0,0 +1,157 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { expect } from "@playwright/test";
+import type { Locator, Page } from "@playwright/test";
+import { BasePage } from "tests/e2e/pages/BasePage";
+
+export type ReprocessBehavior = "All Runs" | "Missing and Errored Runs" | 
"Missing Runs";
+
+export type CreateBackfillOptions = {
+  fromDate: string;
+  reprocessBehavior: ReprocessBehavior;
+  toDate: string;
+};
+
+export type VerifyBackfillOptions = {
+  dagName: string;
+  expectedFromDate: string;
+  expectedToDate: string;
+  reprocessBehavior: ReprocessBehavior;
+};
+
+export class BackfillPage extends BasePage {
+  public readonly backfillDateError: Locator;
+  public readonly backfillFromDateInput: Locator;
+  public readonly backfillModeRadio: Locator;
+  public readonly backfillRunButton: Locator;
+  public readonly backfillsTable: Locator;
+  public readonly backfillToDateInput: Locator;
+  public readonly triggerButton: Locator;
+
+  public constructor(page: Page) {
+    super(page);
+    this.triggerButton = page.locator('button[aria-label="Trigger 
Dag"]:has-text("Trigger")');
+    this.backfillModeRadio = page.locator('label:has-text("Backfill")');
+    this.backfillFromDateInput = 
page.locator('input[type="datetime-local"]').first();
+    this.backfillToDateInput = 
page.locator('input[type="datetime-local"]').nth(1);
+    this.backfillRunButton = page.locator('button:has-text("Run Backfill")');
+    this.backfillsTable = page.locator("table");
+    this.backfillDateError = page.locator('text="Start Date must be before the 
End Date"');
+  }
+
+  public static getBackfillsUrl(dagName: string): string {
+    return `/dags/${dagName}/backfills`;
+  }
+
+  public static getDagDetailUrl(dagName: string): string {
+    return `/dags/${dagName}`;
+  }
+
+  public async createBackfill(dagName: string, options: 
CreateBackfillOptions): Promise<void> {
+    const { fromDate, reprocessBehavior, toDate } = options;
+
+    await this.navigateToDagDetail(dagName);
+    await this.openBackfillDialog();
+
+    await this.backfillFromDateInput.fill(fromDate);
+    await this.backfillToDateInput.fill(toDate);
+
+    await this.selectReprocessBehavior(reprocessBehavior);
+
+    const runsMessage = this.page.locator("text=/\\d+ runs? will be 
triggered|No runs matching/");
+
+    await expect(runsMessage).toBeVisible({ timeout: 10_000 });
+
+    await expect(this.backfillRunButton).toBeEnabled({ timeout: 5000 });
+    await this.backfillRunButton.click();
+
+    const backfillStatus = 
this.page.locator('[data-testid="backfill-status"]');
+
+    await expect(backfillStatus).toBeVisible({ timeout: 10_000 });
+  }
+
+  public async getBackfillsTableRows(): Promise<number> {
+    const rows = this.page.locator("table tbody tr");
+
+    await rows.first().waitFor({ state: "visible", timeout: 10_000 });
+    const count = await rows.count();
+
+    return count;
+  }
+
+  public async isBackfillDateErrorVisible(): Promise<boolean> {
+    return this.backfillDateError.isVisible();
+  }
+
+  public async navigateToBackfillsTab(dagName: string): Promise<void> {
+    await this.navigateTo(BackfillPage.getBackfillsUrl(dagName));
+  }
+
+  public async navigateToDagDetail(dagName: string): Promise<void> {
+    await this.navigateTo(BackfillPage.getDagDetailUrl(dagName));
+  }
+
+  public async openBackfillDialog(): Promise<void> {
+    await this.triggerButton.waitFor({ state: "visible", timeout: 10_000 });
+    await this.triggerButton.click();
+
+    await expect(this.backfillModeRadio).toBeVisible({ timeout: 8000 });
+    await this.backfillModeRadio.click();
+
+    await expect(this.backfillFromDateInput).toBeVisible({ 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();
+  }
+
+  public async verifyBackfillCreated(options: VerifyBackfillOptions): 
Promise<void> {
+    const { dagName, expectedFromDate, expectedToDate, reprocessBehavior } = 
options;
+    // Verify backfill status banner is visible
+    const backfillStatus = 
this.page.locator('[data-testid="backfill-status"]');
+
+    await expect(backfillStatus).toBeVisible();
+
+    // Navigate to backfills tab
+    await this.navigateToBackfillsTab(dagName);
+    await this.waitForPageLoad();
+
+    // Verify at least one backfill row exists
+    const backfillRows = await this.getBackfillsTableRows();
+
+    expect(backfillRows).toBeGreaterThanOrEqual(1);
+
+    // Verify specific backfill row exists with matching dates and behavior
+    const backfillRow = this.page.locator(

Review Comment:
   This is fragile - if date format differs slightly, it won't match. Consider 
using data-testid on table rows or simpler verification.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to