vatsrahul1001 commented on code in PR #60122: URL: https://github.com/apache/airflow/pull/60122#discussion_r2665070473
########## airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts: ########## @@ -0,0 +1,146 @@ +/*! + * 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 { testConfig } from "playwright.config"; +import { DagsPage } from "tests/e2e/pages/DagsPage"; +import { EventsPage } from "tests/e2e/pages/EventsPage"; + +test.describe("Events Page", () => { + let eventsPage: EventsPage; + + test.beforeEach(({ page }) => { + eventsPage = new EventsPage(page); + }); + + test("should verify search input is visible", async () => { + await eventsPage.navigate(); + await eventsPage.waitForEventsTable(); + + // Verify filter bar (containing search functionality) is visible + await expect(eventsPage.filterBar).toBeVisible({ timeout: 10_000 }); + + // Verify the filter button is present (allows adding search filters) + const filterButton = eventsPage.page.locator('button:has-text("Filter")'); + + await expect(filterButton).toBeVisible(); + + // Click the filter button to open the filter menu + await filterButton.click(); + + // Verify filter menu opened - be more specific to target the filter menu + const filterMenu = eventsPage.page.locator('[role="menu"][aria-labelledby*="menu"][data-state="open"]'); + + await expect(filterMenu).toBeVisible({ timeout: 5000 }); + + // Look for text search options in the menu + const textSearchOptions = eventsPage.page.locator( + '[role="menuitem"]:has-text("DAG ID"), [role="menuitem"]:has-text("Event Type"), [role="menuitem"]:has-text("User")', + ); + + const textSearchOptionsCount = await textSearchOptions.count(); + + expect(textSearchOptionsCount).toBeGreaterThan(0); + await expect(textSearchOptions.first()).toBeVisible(); + + // Close the menu by pressing Escape + await eventsPage.page.keyboard.press("Escape"); + + // Wait a moment for menu to close + await eventsPage.page.waitForTimeout(500); + }); + + test("should verify events page", async () => { + await eventsPage.navigate(); + + await expect(async () => { + // To avoid flakiness, we use a promise to wait for the elements to be visible + await expect(eventsPage.eventsPageTitle).toBeVisible({ timeout: 10_000 }); + await eventsPage.waitForEventsTable(); + await expect(eventsPage.eventsTable).toBeVisible({ timeout: 10_000 }); + await eventsPage.verifyTableColumns(); + }).toPass({ timeout: 30_000 }); + }); +}); + +test.describe("Events with Generated Data", () => { + let eventsPage: EventsPage; + let dagsPage: DagsPage; + const testDagId = testConfig.testDag.id; + + test.beforeEach(({ page }) => { + eventsPage = new EventsPage(page); + dagsPage = new DagsPage(page); + }); + + test("should show audit log entries after triggering a DAG", async () => { + test.setTimeout(3 * 60 * 1000); // 3 minutes timeout + + // First, trigger a DAG to generate audit log entries + await dagsPage.triggerDag(testDagId); + await expect(async () => { + // Navigate to events page + await eventsPage.navigate(); + // Wait for table to load + await eventsPage.waitForEventsTable(); + // Verify the log entries contain actual data + await eventsPage.verifyLogEntriesWithData(); + }).toPass({ timeout: 30_000 }); + }); + + test("should verify pagination works with small page size", async () => { + // Navigate to events page with small page size to force pagination + await eventsPage.navigateToPaginatedEventsPage(); + + await eventsPage.waitForEventsTable(); + + await expect(eventsPage.paginationNextButton).toBeVisible({ timeout: 10_000 }); + await expect(eventsPage.paginationPrevButton).toBeVisible({ timeout: 10_000 }); + + // Test pagination functionality - should have enough data to enable next button + await expect(eventsPage.paginationNextButton).toBeEnabled({ timeout: 10_000 }); + + // Click next page - content should change + await eventsPage.clickNextPage(); + await expect(eventsPage.eventsTable).toBeVisible({ timeout: 10_000 }); + + // Click prev page - content should change and previous button should be enabled₹ + await expect(eventsPage.paginationPrevButton).toBeEnabled({ timeout: 5000 }); + + await eventsPage.clickPrevPage(); + await expect(eventsPage.eventsTable).toBeVisible({ timeout: 10_000 }); + }); + + test("should verify column sorting works", async () => { + await eventsPage.navigate(); + await eventsPage.waitForEventsTable(); + + await eventsPage.waitForTimeout(5000); Review Comment: This adds 10 seconds of unnecessary wait time ``` await expect(async () => { const sortIndicator = await eventsPage.getColumnSortIndicator(0); expect(sortIndicator).not.toBe("none"); }).toPass({ timeout: 10_000 }); ``` ########## airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts: ########## @@ -0,0 +1,146 @@ +/*! + * 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 { testConfig } from "playwright.config"; +import { DagsPage } from "tests/e2e/pages/DagsPage"; +import { EventsPage } from "tests/e2e/pages/EventsPage"; + +test.describe("Events Page", () => { + let eventsPage: EventsPage; + + test.beforeEach(({ page }) => { + eventsPage = new EventsPage(page); + }); + + test("should verify search input is visible", async () => { Review Comment: Test only checks that the filter UI exists: - Opens filter menu - Verifies menu items visible - Closes menu This is NOT a filter test - it's just checking the filter UI is present. ########## airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts: ########## @@ -0,0 +1,146 @@ +/*! + * 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 { testConfig } from "playwright.config"; +import { DagsPage } from "tests/e2e/pages/DagsPage"; +import { EventsPage } from "tests/e2e/pages/EventsPage"; + +test.describe("Events Page", () => { + let eventsPage: EventsPage; + + test.beforeEach(({ page }) => { + eventsPage = new EventsPage(page); + }); + + test("should verify search input is visible", async () => { + await eventsPage.navigate(); + await eventsPage.waitForEventsTable(); + + // Verify filter bar (containing search functionality) is visible + await expect(eventsPage.filterBar).toBeVisible({ timeout: 10_000 }); + + // Verify the filter button is present (allows adding search filters) + const filterButton = eventsPage.page.locator('button:has-text("Filter")'); + + await expect(filterButton).toBeVisible(); + + // Click the filter button to open the filter menu + await filterButton.click(); + + // Verify filter menu opened - be more specific to target the filter menu + const filterMenu = eventsPage.page.locator('[role="menu"][aria-labelledby*="menu"][data-state="open"]'); + + await expect(filterMenu).toBeVisible({ timeout: 5000 }); + + // Look for text search options in the menu + const textSearchOptions = eventsPage.page.locator( + '[role="menuitem"]:has-text("DAG ID"), [role="menuitem"]:has-text("Event Type"), [role="menuitem"]:has-text("User")', + ); + + const textSearchOptionsCount = await textSearchOptions.count(); + + expect(textSearchOptionsCount).toBeGreaterThan(0); + await expect(textSearchOptions.first()).toBeVisible(); + + // Close the menu by pressing Escape + await eventsPage.page.keyboard.press("Escape"); + + // Wait a moment for menu to close + await eventsPage.page.waitForTimeout(500); + }); + + test("should verify events page", async () => { + await eventsPage.navigate(); + + await expect(async () => { + // To avoid flakiness, we use a promise to wait for the elements to be visible + await expect(eventsPage.eventsPageTitle).toBeVisible({ timeout: 10_000 }); + await eventsPage.waitForEventsTable(); + await expect(eventsPage.eventsTable).toBeVisible({ timeout: 10_000 }); + await eventsPage.verifyTableColumns(); + }).toPass({ timeout: 30_000 }); + }); +}); + +test.describe("Events with Generated Data", () => { + let eventsPage: EventsPage; + let dagsPage: DagsPage; + const testDagId = testConfig.testDag.id; + + test.beforeEach(({ page }) => { + eventsPage = new EventsPage(page); + dagsPage = new DagsPage(page); + }); + + test("should show audit log entries after triggering a DAG", async () => { + test.setTimeout(3 * 60 * 1000); // 3 minutes timeout + + // First, trigger a DAG to generate audit log entries + await dagsPage.triggerDag(testDagId); Review Comment: Move trigger dag to beforeAll ``` test.describe("Events with Generated Data", () => { const testDagId = testConfig.testDag.id; test.beforeAll(async ({ browser }) => { test.setTimeout(3 * 60 * 1000); const context = await browser.newContext({ storageState: AUTH_FILE }); const page = await context.newPage(); const dagsPage = new DagsPage(page); await dagsPage.triggerDag(testDagId); await context.close(); }); test.beforeEach(({ page }) => { eventsPage = new EventsPage(page); }); ``` ########## airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts: ########## @@ -0,0 +1,238 @@ +/*! + * 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, type Locator, type Page } from "@playwright/test"; +import { BasePage } from "tests/e2e/pages/BasePage"; + +/** + * Events Page Object + */ +export class EventsPage extends BasePage { + public static get eventsListPaginatedURL(): string { + return "events?limit=1&offset=1"; + } + + // Page URLs + public static get eventsListUrl(): string { + return "/events"; + } + + public readonly dagIdColumn: Locator; + public readonly eventsPageTitle: Locator; + public readonly eventsTable: Locator; + public readonly eventTypeColumn: Locator; + public readonly extraColumn: Locator; + public readonly filterBar: Locator; + public readonly mapIndexColumn: Locator; + public readonly paginationNextButton: Locator; + public readonly paginationPrevButton: Locator; + public readonly runIdColumn: Locator; + public readonly taskIdColumn: Locator; + public readonly timestampColumn: Locator; + public readonly tryNumberColumn: Locator; + public readonly userColumn: Locator; + + public constructor(page: Page) { + super(page); + this.eventsPageTitle = page.locator('h2:has-text("Audit Log")'); + this.eventsTable = page.getByRole("table"); + this.filterBar = page.locator('div:has(button:has-text("Filter"))').first(); + // Use table header selectors - will check for presence of key columns + this.timestampColumn = page.locator("thead th").first(); Review Comment: If column order changes, all tests break. Maybe we can use something like below ``` this.timestampColumn = page.getByRole("columnheader", { name: /when/i }); this.eventTypeColumn = page.getByRole("columnheader", { name: /event/i }); this.userColumn = page.getByRole("columnheader", { name: /user/i }); ``` ########## airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts: ########## @@ -0,0 +1,146 @@ +/*! + * 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 { testConfig } from "playwright.config"; +import { DagsPage } from "tests/e2e/pages/DagsPage"; +import { EventsPage } from "tests/e2e/pages/EventsPage"; + +test.describe("Events Page", () => { + let eventsPage: EventsPage; + + test.beforeEach(({ page }) => { + eventsPage = new EventsPage(page); + }); + + test("should verify search input is visible", async () => { + await eventsPage.navigate(); + await eventsPage.waitForEventsTable(); + + // Verify filter bar (containing search functionality) is visible + await expect(eventsPage.filterBar).toBeVisible({ timeout: 10_000 }); + + // Verify the filter button is present (allows adding search filters) + const filterButton = eventsPage.page.locator('button:has-text("Filter")'); + + await expect(filterButton).toBeVisible(); + + // Click the filter button to open the filter menu + await filterButton.click(); + + // Verify filter menu opened - be more specific to target the filter menu + const filterMenu = eventsPage.page.locator('[role="menu"][aria-labelledby*="menu"][data-state="open"]'); + + await expect(filterMenu).toBeVisible({ timeout: 5000 }); + + // Look for text search options in the menu + const textSearchOptions = eventsPage.page.locator( + '[role="menuitem"]:has-text("DAG ID"), [role="menuitem"]:has-text("Event Type"), [role="menuitem"]:has-text("User")', + ); + + const textSearchOptionsCount = await textSearchOptions.count(); + + expect(textSearchOptionsCount).toBeGreaterThan(0); + await expect(textSearchOptions.first()).toBeVisible(); + + // Close the menu by pressing Escape + await eventsPage.page.keyboard.press("Escape"); + + // Wait a moment for menu to close + await eventsPage.page.waitForTimeout(500); + }); + + test("should verify events page", async () => { + await eventsPage.navigate(); + + await expect(async () => { + // To avoid flakiness, we use a promise to wait for the elements to be visible + await expect(eventsPage.eventsPageTitle).toBeVisible({ timeout: 10_000 }); + await eventsPage.waitForEventsTable(); + await expect(eventsPage.eventsTable).toBeVisible({ timeout: 10_000 }); + await eventsPage.verifyTableColumns(); + }).toPass({ timeout: 30_000 }); + }); +}); + +test.describe("Events with Generated Data", () => { + let eventsPage: EventsPage; + let dagsPage: DagsPage; + const testDagId = testConfig.testDag.id; + + test.beforeEach(({ page }) => { + eventsPage = new EventsPage(page); + dagsPage = new DagsPage(page); + }); + + test("should show audit log entries after triggering a DAG", async () => { + test.setTimeout(3 * 60 * 1000); // 3 minutes timeout + + // First, trigger a DAG to generate audit log entries + await dagsPage.triggerDag(testDagId); + await expect(async () => { + // Navigate to events page + await eventsPage.navigate(); + // Wait for table to load + await eventsPage.waitForEventsTable(); + // Verify the log entries contain actual data + await eventsPage.verifyLogEntriesWithData(); + }).toPass({ timeout: 30_000 }); + }); + + test("should verify pagination works with small page size", async () => { + // Navigate to events page with small page size to force pagination + await eventsPage.navigateToPaginatedEventsPage(); + + await eventsPage.waitForEventsTable(); + + await expect(eventsPage.paginationNextButton).toBeVisible({ timeout: 10_000 }); + await expect(eventsPage.paginationPrevButton).toBeVisible({ timeout: 10_000 }); + + // Test pagination functionality - should have enough data to enable next button + await expect(eventsPage.paginationNextButton).toBeEnabled({ timeout: 10_000 }); + + // Click next page - content should change + await eventsPage.clickNextPage(); + await expect(eventsPage.eventsTable).toBeVisible({ timeout: 10_000 }); + + // Click prev page - content should change and previous button should be enabled₹ + await expect(eventsPage.paginationPrevButton).toBeEnabled({ timeout: 5000 }); + + await eventsPage.clickPrevPage(); + await expect(eventsPage.eventsTable).toBeVisible({ timeout: 10_000 }); + }); + + test("should verify column sorting works", async () => { Review Comment: This doesn't verify the data is actually sorted. Capture data before/after and verify order -- 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]
