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


##########
airflow-core/src/airflow/ui/tests/e2e/specs/dag-audit-log.spec.ts:
##########
@@ -0,0 +1,133 @@
+/*!
+ * 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 { AUTH_FILE, testConfig } from "playwright.config";
+import { DagsPage } from "tests/e2e/pages/DagsPage";
+import { EventsPage } from "tests/e2e/pages/EventsPage";
+
+test.describe("DAG Audit Log", () => {
+  let eventsPage: EventsPage;
+
+  const testDagId = testConfig.testDag.id;
+
+  test.setTimeout(60_000);
+
+  test.beforeAll(async ({ browser }) => {
+    test.setTimeout(7 * 60 * 1000);
+    const context = await browser.newContext({ storageState: AUTH_FILE });
+    const page = await context.newPage();
+    const setupDagsPage = new DagsPage(page);
+    const setupEventsPage = new EventsPage(page);
+
+    for (let i = 0; i < 10; i++) {
+      await setupDagsPage.triggerDag(testDagId);
+    }
+
+    await setupEventsPage.navigateToAuditLog(testDagId);
+    await context.close();
+  });
+
+  test.beforeEach(({ page }) => {
+    eventsPage = new EventsPage(page);
+  });
+
+  test("should navigate to audit log tab and display table", async () => {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    const rowCount = await eventsPage.getEventLogCount();
+
+    await expect(eventsPage.eventsTable).toBeVisible();
+
+    const isTableVisible = await eventsPage.isAuditLogTableVisible();
+
+    expect(isTableVisible).toBe(true);
+    expect(rowCount).toBeGreaterThanOrEqual(0);
+  });
+
+  test("should display all expected columns in audit log table", async () => {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    await expect(eventsPage.whenColumn).toBeVisible();
+    await expect(eventsPage.eventColumn).toBeVisible();
+    await expect(eventsPage.ownerColumn).toBeVisible();
+    await expect(eventsPage.extraColumn).toBeVisible();
+
+    const dagIdColumn = eventsPage.eventsTable.locator('th:has-text("DAG 
ID")');
+
+    await expect(dagIdColumn).not.toBeVisible();
+  });
+
+  test("should display audit log entries with valid data", async () => {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    const rows = await eventsPage.getEventLogRows();
+
+    if (rows.length > 0) {
+      const [firstRow] = rows;
+
+      if (firstRow) {
+        const cells = firstRow.locator("td");
+        const cellCount = await cells.count();
+
+        expect(cellCount).toBeGreaterThan(0);
+
+        const allTextContents = await cells.allTextContents();
+        const hasContent = allTextContents.some((text) => text.trim().length > 
0);
+
+        expect(hasContent).toBe(true);

Review Comment:
   Just checks "something exists" - doesn't verify actual event data like event 
type, timestamp format, etc.
   



##########
airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:
##########
@@ -0,0 +1,201 @@
+/*!
+ * 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 type { Locator, Page } from "@playwright/test";
+import { BasePage } from "tests/e2e/pages/BasePage";
+
+/**
+ * Events/Audit Log Page Object
+ */
+export class EventsPage extends BasePage {
+  // Locators
+  public readonly auditLogTab: Locator;
+  public readonly eventColumn: Locator;
+  public readonly eventsTable: Locator;
+  public readonly extraColumn: Locator;
+  public readonly ownerColumn: Locator;
+  public readonly paginationNextButton: Locator;
+  public readonly paginationPrevButton: Locator;
+  public readonly skeletonLoader: Locator;
+  public readonly tableRows: Locator;
+  public readonly whenColumn: Locator;
+
+  public constructor(page: Page) {
+    super(page);
+    this.auditLogTab = page.locator('a[href$="/events"]');
+    this.eventsTable = page.locator('[data-testid="table-list"]');
+    this.eventColumn = this.eventsTable.locator('th:has-text("Event")');
+    this.extraColumn = this.eventsTable.locator('th:has-text("Extra")');
+    this.ownerColumn = this.eventsTable.locator('th:has-text("User")');
+    this.paginationNextButton = page.locator('[data-testid="next"]');
+    this.paginationPrevButton = page.locator('[data-testid="prev"]');
+    this.skeletonLoader = page.locator('[data-testid="skeleton"]');
+    this.tableRows = this.eventsTable.locator("tbody tr");
+    this.whenColumn = this.eventsTable.locator('th:has-text("When")');
+  }
+
+  /**
+   * Build URL for DAG-specific events page
+   */
+  public static getEventsUrl(dagId: string): string {
+    return `/dags/${dagId}/events`;
+  }
+
+  /**
+   * Click a column header to sort
+   */
+  public async clickColumnToSort(columnName: "Event" | "User" | "When"): 
Promise<void> {
+    const columnHeader = 
this.eventsTable.locator(`th:has-text("${columnName}")`);
+    const sortButton = columnHeader.locator('button[aria-label="sort"]');
+
+    await sortButton.click();
+    await this.page.waitForTimeout(2000);
+  }
+
+  /**
+   * Click next page button
+   */
+  public async clickNextPage(): Promise<void> {
+    await this.paginationNextButton.click();
+    await this.waitForTableLoad();
+  }
+
+  /**
+   * Click previous page button
+   */
+  public async clickPrevPage(): Promise<void> {
+    await this.paginationPrevButton.click();
+    await this.waitForTableLoad();
+  }
+
+  /**
+   * Get count of event log rows
+   */
+  public async getEventLogCount(): Promise<number> {
+    try {
+      return await this.tableRows.count();
+    } catch {
+      return 0;
+    }
+  }
+
+  /**
+   * Get all event log rows
+   */
+  public async getEventLogRows(): Promise<Array<Locator>> {
+    try {
+      const count = await this.tableRows.count();
+
+      if (count === 0) {
+        return [];
+      }
+
+      return this.tableRows.all();
+    } catch {
+      return [];
+    }
+  }
+
+  /**
+   * Get all event types from the current page
+   */
+  public async getEventTypes(): Promise<Array<string>> {
+    try {
+      const rows = await this.getEventLogRows();
+
+      if (rows.length === 0) {
+        return [];
+      }
+
+      const eventTypes: Array<string> = [];
+
+      for (const row of rows) {
+        const cells = row.locator("td");
+        const eventCell = cells.nth(1);
+        const text = await eventCell.textContent();
+
+        if (text !== null) {
+          eventTypes.push(text.trim());
+        }
+      }
+
+      return eventTypes;
+    } catch {
+      return [];
+    }
+  }
+
+  /**
+   * Check if next page is available
+   */
+  public async hasNextPage(): Promise<boolean> {
+    try {
+      return await this.paginationNextButton.isEnabled();
+    } catch {
+      return false;
+    }
+  }
+
+  /**
+   * Check if previous page is available
+   */
+  public async hasPreviousPage(): Promise<boolean> {
+    try {
+      return await this.paginationPrevButton.isEnabled();
+    } catch {
+      return false;
+    }
+  }
+
+  /**
+   * Check if audit log table is visible
+   */
+  public async isAuditLogTableVisible(): Promise<boolean> {
+    try {
+      await this.eventsTable.waitFor({ state: "visible", timeout: 5000 });
+
+      return true;
+    } catch {
+      return false;

Review Comment:
   Methods like isAuditLogTableVisible(), hasNextPage(), getEventLogCount() 
catch errors and return false This makes debugging difficult and can cause 
tests to pass when they shouldn't.



##########
airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:
##########
@@ -0,0 +1,201 @@
+/*!
+ * 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 type { Locator, Page } from "@playwright/test";
+import { BasePage } from "tests/e2e/pages/BasePage";
+
+/**
+ * Events/Audit Log Page Object
+ */
+export class EventsPage extends BasePage {
+  // Locators
+  public readonly auditLogTab: Locator;
+  public readonly eventColumn: Locator;
+  public readonly eventsTable: Locator;
+  public readonly extraColumn: Locator;
+  public readonly ownerColumn: Locator;
+  public readonly paginationNextButton: Locator;
+  public readonly paginationPrevButton: Locator;
+  public readonly skeletonLoader: Locator;
+  public readonly tableRows: Locator;
+  public readonly whenColumn: Locator;
+
+  public constructor(page: Page) {
+    super(page);
+    this.auditLogTab = page.locator('a[href$="/events"]');
+    this.eventsTable = page.locator('[data-testid="table-list"]');
+    this.eventColumn = this.eventsTable.locator('th:has-text("Event")');
+    this.extraColumn = this.eventsTable.locator('th:has-text("Extra")');
+    this.ownerColumn = this.eventsTable.locator('th:has-text("User")');
+    this.paginationNextButton = page.locator('[data-testid="next"]');
+    this.paginationPrevButton = page.locator('[data-testid="prev"]');
+    this.skeletonLoader = page.locator('[data-testid="skeleton"]');
+    this.tableRows = this.eventsTable.locator("tbody tr");
+    this.whenColumn = this.eventsTable.locator('th:has-text("When")');
+  }
+
+  /**
+   * Build URL for DAG-specific events page
+   */
+  public static getEventsUrl(dagId: string): string {
+    return `/dags/${dagId}/events`;
+  }
+
+  /**
+   * Click a column header to sort
+   */
+  public async clickColumnToSort(columnName: "Event" | "User" | "When"): 
Promise<void> {
+    const columnHeader = 
this.eventsTable.locator(`th:has-text("${columnName}")`);
+    const sortButton = columnHeader.locator('button[aria-label="sort"]');
+
+    await sortButton.click();
+    await this.page.waitForTimeout(2000);

Review Comment:
   Instead of hardcoding wait. We can wait for specific condition (table 
reload, URL change) instead. 



##########
airflow-core/src/airflow/ui/tests/e2e/specs/dag-audit-log.spec.ts:
##########
@@ -0,0 +1,133 @@
+/*!
+ * 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 { AUTH_FILE, testConfig } from "playwright.config";
+import { DagsPage } from "tests/e2e/pages/DagsPage";
+import { EventsPage } from "tests/e2e/pages/EventsPage";
+
+test.describe("DAG Audit Log", () => {
+  let eventsPage: EventsPage;
+
+  const testDagId = testConfig.testDag.id;
+
+  test.setTimeout(60_000);
+
+  test.beforeAll(async ({ browser }) => {
+    test.setTimeout(7 * 60 * 1000);
+    const context = await browser.newContext({ storageState: AUTH_FILE });
+    const page = await context.newPage();
+    const setupDagsPage = new DagsPage(page);
+    const setupEventsPage = new EventsPage(page);
+
+    for (let i = 0; i < 10; i++) {

Review Comment:
   Very slow (could take 70+ minutes with 7-minute timeout per trigger)
   If one trigger fails, all tests fail
   We can Reduce to 2-3 triggers



##########
airflow-core/src/airflow/ui/tests/e2e/pages/DagsPage.ts:
##########
@@ -64,15 +64,13 @@ export class DagsPage extends BasePage {
    */
   public async clickNextPage(): Promise<void> {
     await this.paginationNextButton.click();
-    await this.waitForDagList();

Review Comment:
   Removing waitForDagList() could break other tests that depend on pagination. 
Why was this changed?



##########
airflow-core/src/airflow/ui/tests/e2e/specs/dag-audit-log.spec.ts:
##########
@@ -0,0 +1,133 @@
+/*!
+ * 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 { AUTH_FILE, testConfig } from "playwright.config";
+import { DagsPage } from "tests/e2e/pages/DagsPage";
+import { EventsPage } from "tests/e2e/pages/EventsPage";
+
+test.describe("DAG Audit Log", () => {
+  let eventsPage: EventsPage;
+
+  const testDagId = testConfig.testDag.id;
+
+  test.setTimeout(60_000);
+
+  test.beforeAll(async ({ browser }) => {
+    test.setTimeout(7 * 60 * 1000);
+    const context = await browser.newContext({ storageState: AUTH_FILE });
+    const page = await context.newPage();
+    const setupDagsPage = new DagsPage(page);
+    const setupEventsPage = new EventsPage(page);
+
+    for (let i = 0; i < 10; i++) {
+      await setupDagsPage.triggerDag(testDagId);
+    }
+
+    await setupEventsPage.navigateToAuditLog(testDagId);
+    await context.close();
+  });
+
+  test.beforeEach(({ page }) => {
+    eventsPage = new EventsPage(page);
+  });
+
+  test("should navigate to audit log tab and display table", async () => {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    const rowCount = await eventsPage.getEventLogCount();
+
+    await expect(eventsPage.eventsTable).toBeVisible();
+
+    const isTableVisible = await eventsPage.isAuditLogTableVisible();
+
+    expect(isTableVisible).toBe(true);
+    expect(rowCount).toBeGreaterThanOrEqual(0);
+  });
+
+  test("should display all expected columns in audit log table", async () => {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    await expect(eventsPage.whenColumn).toBeVisible();
+    await expect(eventsPage.eventColumn).toBeVisible();
+    await expect(eventsPage.ownerColumn).toBeVisible();
+    await expect(eventsPage.extraColumn).toBeVisible();
+
+    const dagIdColumn = eventsPage.eventsTable.locator('th:has-text("DAG 
ID")');
+
+    await expect(dagIdColumn).not.toBeVisible();
+  });
+
+  test("should display audit log entries with valid data", async () => {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    const rows = await eventsPage.getEventLogRows();
+
+    if (rows.length > 0) {
+      const [firstRow] = rows;
+
+      if (firstRow) {
+        const cells = firstRow.locator("td");
+        const cellCount = await cells.count();
+
+        expect(cellCount).toBeGreaterThan(0);
+
+        const allTextContents = await cells.allTextContents();
+        const hasContent = allTextContents.some((text) => text.trim().length > 
0);
+
+        expect(hasContent).toBe(true);
+      }
+    }
+  });
+
+  test("should paginate through audit log entries", async () => {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    const hasNext = await eventsPage.hasNextPage();
+
+    if (hasNext) {

Review Comment:
   If pagination isn't available, test passes without testing anything.



##########
airflow-core/src/airflow/ui/tests/e2e/specs/dag-audit-log.spec.ts:
##########
@@ -0,0 +1,133 @@
+/*!
+ * 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 { AUTH_FILE, testConfig } from "playwright.config";
+import { DagsPage } from "tests/e2e/pages/DagsPage";
+import { EventsPage } from "tests/e2e/pages/EventsPage";
+
+test.describe("DAG Audit Log", () => {
+  let eventsPage: EventsPage;
+
+  const testDagId = testConfig.testDag.id;
+
+  test.setTimeout(60_000);
+
+  test.beforeAll(async ({ browser }) => {
+    test.setTimeout(7 * 60 * 1000);
+    const context = await browser.newContext({ storageState: AUTH_FILE });
+    const page = await context.newPage();
+    const setupDagsPage = new DagsPage(page);
+    const setupEventsPage = new EventsPage(page);
+
+    for (let i = 0; i < 10; i++) {
+      await setupDagsPage.triggerDag(testDagId);
+    }
+
+    await setupEventsPage.navigateToAuditLog(testDagId);
+    await context.close();
+  });
+
+  test.beforeEach(({ page }) => {
+    eventsPage = new EventsPage(page);
+  });
+
+  test("should navigate to audit log tab and display table", async () => {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    const rowCount = await eventsPage.getEventLogCount();
+
+    await expect(eventsPage.eventsTable).toBeVisible();
+
+    const isTableVisible = await eventsPage.isAuditLogTableVisible();
+
+    expect(isTableVisible).toBe(true);
+    expect(rowCount).toBeGreaterThanOrEqual(0);
+  });
+
+  test("should display all expected columns in audit log table", async () => {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    await expect(eventsPage.whenColumn).toBeVisible();
+    await expect(eventsPage.eventColumn).toBeVisible();
+    await expect(eventsPage.ownerColumn).toBeVisible();
+    await expect(eventsPage.extraColumn).toBeVisible();
+
+    const dagIdColumn = eventsPage.eventsTable.locator('th:has-text("DAG 
ID")');
+
+    await expect(dagIdColumn).not.toBeVisible();
+  });
+
+  test("should display audit log entries with valid data", async () => {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    const rows = await eventsPage.getEventLogRows();
+
+    if (rows.length > 0) {
+      const [firstRow] = rows;
+
+      if (firstRow) {
+        const cells = firstRow.locator("td");
+        const cellCount = await cells.count();
+
+        expect(cellCount).toBeGreaterThan(0);
+
+        const allTextContents = await cells.allTextContents();
+        const hasContent = allTextContents.some((text) => text.trim().length > 
0);
+
+        expect(hasContent).toBe(true);
+      }
+    }
+  });
+
+  test("should paginate through audit log entries", async () => {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    const hasNext = await eventsPage.hasNextPage();
+
+    if (hasNext) {
+      const initialEvents = await eventsPage.getEventTypes();
+
+      await eventsPage.clickNextPage();
+
+      const nextPageEvents = await eventsPage.getEventTypes();
+
+      expect(nextPageEvents).not.toEqual(initialEvents);
+
+      await eventsPage.clickPrevPage();
+
+      const backToFirstEvents = await eventsPage.getEventTypes();
+
+      expect(backToFirstEvents).toEqual(initialEvents);
+    }
+  });
+
+  test("should sort audit log entries when clicking column header", async () 
=> {
+    await eventsPage.navigateToAuditLog(testDagId);
+
+    const initialEvents = await eventsPage.getEventTypes();
+
+    await eventsPage.clickColumnToSort("Event");
+
+    const sortedEvents = await eventsPage.getEventTypes();
+
+    const orderChanged = JSON.stringify(initialEvents) !== 
JSON.stringify(sortedEvents);
+
+    expect(orderChanged).toBe(true);

Review Comment:
   Only checks order changed, not that it's correctly sorted. Could pass even 
if sorting is broken.
   



-- 
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