Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@
* under the License.
*/
import type { Locator, Page } from "@playwright/test";
import { expect } from "@playwright/test";
import { BasePage } from "tests/e2e/pages/BasePage";

export class EventsPage extends BasePage {
public readonly eventColumn: Locator;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are defined but not directly used:
eventColumn, extraColumn, ownerColumn, whenColumn
clickColumnHeader() (only clickColumnToSort() is used)

Consider removing if not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, these columns are being used in dag-audit-log.spec.ts and its usage is added into the previous diff, hence will be keeping this.

public readonly eventsPageTitle: Locator;
public readonly eventsTable: Locator;
public readonly extraColumn: Locator;
public readonly filterBar: Locator;
public readonly ownerColumn: Locator;
public readonly paginationNextButton: Locator;
public readonly paginationPrevButton: Locator;
Expand All @@ -34,9 +37,14 @@

public constructor(page: Page) {
super(page);
this.eventsPageTitle = page.locator('h2:has-text("Audit Log")');
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.filterBar = page
.locator("div")
.filter({ has: page.locator('button:has-text("Filter")') })
.first();
this.ownerColumn = this.eventsTable.locator('th:has-text("User")');
this.paginationNextButton = page.locator('[data-testid="next"]');
this.paginationPrevButton = page.locator('[data-testid="prev"]');
Expand All @@ -48,6 +56,28 @@
return `/dags/${dagId}/events`;
}

public async addFilter(filterName: string): Promise<void> {
const filterButton = this.page.locator('button:has-text("Filter")');

await filterButton.click();

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

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

const menuItem = filterMenu.locator(`[role="menuitem"]:has-text("${filterName}")`);

await menuItem.click();
}

public async clickColumnHeader(columnKey: string): Promise<void> {
const columnHeader = this.eventsTable.locator("th").filter({ hasText: new RegExp(columnKey, "i") });
const sortButton = columnHeader.locator('button[aria-label="sort"]');

await sortButton.click();
await this.waitForTableLoad();
}

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"]');
Expand All @@ -64,7 +94,7 @@
}

public async clickPrevPage(): Promise<void> {
await this.paginationPrevButton.click();

Check failure on line 97 in airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts

View workflow job for this annotation

GitHub Actions / Additional PROD image tests / Firefox UI e2e tests with PROD image / Firefox UI e2e tests

[firefox] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works

1) [firefox] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── TimeoutError: locator.click: Timeout 10000ms exceeded. Call log: - waiting for locator('[data-testid="prev"]') - locator resolved to <button dir="ltr" type="button" data-testid="prev" data-scope="pagination" data-part="prev-trigger" id="pagination:_r_a_:prev" aria-label="previous page" class="chakra-button css-hlgnym">…</button> - attempting click action - waiting for element to be visible, enabled and stable - element is not stable - retrying click action - waiting for element to be visible, enabled and stable - element is not enabled - retrying click action - waiting 20ms 2 × waiting for element to be visible, enabled and stable - element is not enabled - retrying click action - waiting 100ms 17 × waiting for element to be visible, enabled and stable - element is not enabled - retrying click action - waiting 500ms at ../pages/EventsPage.ts:97 95 | 96 | public async clickPrevPage(): Promise<void> { > 97 | await this.paginationPrevButton.click(); | ^ 98 | await this.waitForTableLoad(); 99 | await this.ensureUrlParams(); 100 | } at EventsPage.clickPrevPage (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:97:37) at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:184:18) at /home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts:123:27

Check failure on line 97 in airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts

View workflow job for this annotation

GitHub Actions / Additional PROD image tests / Chromium UI e2e tests with PROD image / Chromium UI e2e tests

[chromium] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works

1) [chromium] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── TimeoutError: locator.click: Timeout 10000ms exceeded. Call log: - waiting for locator('[data-testid="prev"]') - locator resolved to <button dir="ltr" type="button" data-testid="prev" data-scope="pagination" data-part="prev-trigger" id="pagination:_r_a_:prev" aria-label="previous page" class="chakra-button css-hlgnym">…</button> - attempting click action - waiting for element to be visible, enabled and stable - element is visible, enabled and stable - scrolling into view if needed - done scrolling - <td class="chakra-table__cell css-1luypx0">…</td> from <table data-testid="table-list" class="chakra-table__root css-a2tetu">…</table> subtree intercepts pointer events - retrying click action - waiting for element to be visible, enabled and stable - element is not enabled - retrying click action - waiting 20ms 2 × waiting for element to be visible, enabled and stable - element is not enabled - retrying click action - waiting 100ms 18 × waiting for element to be visible, enabled and stable - element is not enabled - retrying click action - waiting 500ms at ../pages/EventsPage.ts:97 95 | 96 | public async clickPrevPage(): Promise<void> { > 97 | await this.paginationPrevButton.click(); | ^ 98 | await this.waitForTableLoad(); 99 | await this.ensureUrlParams(); 100 | } at EventsPage.clickPrevPage (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:97:37) at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:184:18) at /home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts:123:27

Check failure on line 97 in airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts

View workflow job for this annotation

GitHub Actions / Additional PROD image tests / Chromium UI e2e tests with PROD image / Chromium UI e2e tests

[chromium] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works

1) [chromium] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── TimeoutError: locator.click: Timeout 10000ms exceeded. Call log: - waiting for locator('[data-testid="prev"]') - locator resolved to <button dir="ltr" type="button" data-testid="prev" data-scope="pagination" data-part="prev-trigger" id="pagination:_r_a_:prev" aria-label="previous page" class="chakra-button css-hlgnym">…</button> - attempting click action - waiting for element to be visible, enabled and stable - element is not stable - retrying click action - waiting for element to be visible, enabled and stable - element is not enabled - retrying click action - waiting 20ms 2 × waiting for element to be visible, enabled and stable - element is not enabled - retrying click action - waiting 100ms 18 × waiting for element to be visible, enabled and stable - element is not enabled - retrying click action - waiting 500ms at ../pages/EventsPage.ts:97 95 | 96 | public async clickPrevPage(): Promise<void> { > 97 | await this.paginationPrevButton.click(); | ^ 98 | await this.waitForTableLoad(); 99 | await this.ensureUrlParams(); 100 | } at EventsPage.clickPrevPage (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:97:37) at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:184:18) at /home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts:123:27
await this.waitForTableLoad();
await this.ensureUrlParams();
}
Expand All @@ -80,6 +110,35 @@
return row.locator("td").nth(index);
}

public async getCellContent(rowIndex: number, cellIndex: number): Promise<string> {
const row = this.tableRows.nth(rowIndex);
const cell = row.locator("td").nth(cellIndex);
const content = await cell.textContent();

return content?.trim() ?? "";
}

public async getColumnSortIndicator(columnKey: string): Promise<"asc" | "desc" | "none"> {
const columnHeader = this.eventsTable.locator("th").filter({ hasText: new RegExp(columnKey, "i") });
const sortSvg = columnHeader.locator('svg[aria-label*="sorted"]');
const svgCount = await sortSvg.count();

if (svgCount > 0) {
const ariaLabel = (await sortSvg.first().getAttribute("aria-label")) ?? "";

if (ariaLabel) {
if (ariaLabel.includes("ascending")) {
return "asc";
}
if (ariaLabel.includes("descending")) {
return "desc";
}
}
}

return "none";
}

public async getEventLogRows(): Promise<Array<Locator>> {
const count = await this.tableRows.count();

Expand All @@ -101,7 +160,7 @@

for (const row of rows) {
const eventCell = await this.getCellByColumnName(row, "Event");
const text = await eventCell.textContent();

Check failure on line 163 in airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts

View workflow job for this annotation

GitHub Actions / Additional PROD image tests / Firefox UI e2e tests with PROD image / Firefox UI e2e tests

[firefox] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works

1) [firefox] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── TimeoutError: locator.textContent: Timeout 10000ms exceeded. Call log: - waiting for locator('[data-testid="table-list"]').locator('tbody tr').nth(9).locator('td').nth(1) at ../pages/EventsPage.ts:163 161 | for (const row of rows) { 162 | const eventCell = await this.getCellByColumnName(row, "Event"); > 163 | const text = await eventCell.textContent(); | ^ 164 | 165 | if (text !== null) { 166 | eventTypes.push(text.trim()); at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:163:36) at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:178:26) at /home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts:123:27

Check failure on line 163 in airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts

View workflow job for this annotation

GitHub Actions / Additional PROD image tests / Firefox UI e2e tests with PROD image / Firefox UI e2e tests

[firefox] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works

1) [firefox] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works TimeoutError: locator.textContent: Timeout 10000ms exceeded. Call log: - waiting for locator('[data-testid="table-list"]').locator('tbody tr').nth(4).locator('td').nth(1) at ../pages/EventsPage.ts:163 161 | for (const row of rows) { 162 | const eventCell = await this.getCellByColumnName(row, "Event"); > 163 | const text = await eventCell.textContent(); | ^ 164 | 165 | if (text !== null) { 166 | eventTypes.push(text.trim()); at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:163:36) at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:178:26) at /home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts:123:27

Check failure on line 163 in airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts

View workflow job for this annotation

GitHub Actions / Additional PROD image tests / WebKit UI e2e tests with PROD image / WebKit UI e2e tests

[webkit] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works

1) [webkit] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── TimeoutError: locator.textContent: Timeout 10000ms exceeded. Call log: - waiting for locator('[data-testid="table-list"]').locator('tbody tr').nth(11).locator('td').nth(1) at ../pages/EventsPage.ts:163 161 | for (const row of rows) { 162 | const eventCell = await this.getCellByColumnName(row, "Event"); > 163 | const text = await eventCell.textContent(); | ^ 164 | 165 | if (text !== null) { 166 | eventTypes.push(text.trim()); at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:163:36) at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:178:26) at /home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts:123:27

Check failure on line 163 in airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts

View workflow job for this annotation

GitHub Actions / Additional PROD image tests / WebKit UI e2e tests with PROD image / WebKit UI e2e tests

[webkit] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works

1) [webkit] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── TimeoutError: locator.textContent: Timeout 10000ms exceeded. Call log: - waiting for locator('[data-testid="table-list"]').locator('tbody tr').nth(9).locator('td').nth(1) at ../pages/EventsPage.ts:163 161 | for (const row of rows) { 162 | const eventCell = await this.getCellByColumnName(row, "Event"); > 163 | const text = await eventCell.textContent(); | ^ 164 | 165 | if (text !== null) { 166 | eventTypes.push(text.trim()); at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:163:36) at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:178:26) at /home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts:159:26

Check failure on line 163 in airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts

View workflow job for this annotation

GitHub Actions / Additional PROD image tests / WebKit UI e2e tests with PROD image / WebKit UI e2e tests

[webkit] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works

1) [webkit] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works TimeoutError: locator.textContent: Timeout 10000ms exceeded. Call log: - waiting for locator('[data-testid="table-list"]').locator('tbody tr').nth(10).locator('td').nth(1) at ../pages/EventsPage.ts:163 161 | for (const row of rows) { 162 | const eventCell = await this.getCellByColumnName(row, "Event"); > 163 | const text = await eventCell.textContent(); | ^ 164 | 165 | if (text !== null) { 166 | eventTypes.push(text.trim()); at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:163:36) at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:178:26) at /home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts:123:27

Check failure on line 163 in airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts

View workflow job for this annotation

GitHub Actions / Additional PROD image tests / Chromium UI e2e tests with PROD image / Chromium UI e2e tests

[chromium] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works

1) [chromium] › tests/e2e/specs/events-page.spec.ts:119:3 › Events with Generated Data › verify column sorting works TimeoutError: locator.textContent: Timeout 10000ms exceeded. Call log: - waiting for locator('[data-testid="table-list"]').locator('tbody tr').nth(5).locator('td').nth(1) at ../pages/EventsPage.ts:163 161 | for (const row of rows) { 162 | const eventCell = await this.getCellByColumnName(row, "Event"); > 163 | const text = await eventCell.textContent(); | ^ 164 | 165 | if (text !== null) { 166 | eventTypes.push(text.trim()); at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:163:36) at EventsPage.getEventTypes (/home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/pages/EventsPage.ts:178:26) at /home/runner/work/airflow/airflow/airflow-core/src/airflow/ui/tests/e2e/specs/events-page.spec.ts:123:27

if (text !== null) {
eventTypes.push(text.trim());
Expand All @@ -128,6 +187,14 @@
return allEventTypes;
}

public getFilterPill(filterLabel: string): Locator {
return this.page.locator(`button:has-text("${filterLabel}:")`);
}

public async getTableRowCount(): Promise<number> {
return this.tableRows.count();
}

public async hasNextPage(): Promise<boolean> {
const count = await this.paginationNextButton.count();

Expand All @@ -138,6 +205,11 @@
return await this.paginationNextButton.isEnabled();
}

public async navigate(): Promise<void> {
await this.navigateTo("/events");
await this.waitForTableLoad();
}

public async navigateToAuditLog(dagId: string, limit?: number): Promise<void> {
this.currentDagId = dagId;
this.currentLimit = limit;
Expand All @@ -152,6 +224,71 @@
await this.waitForTableLoad();
}

public async navigateToPaginatedEventsPage(limit: number = 5): Promise<void> {
await this.page.goto(`/events?offset=0&limit=${limit}`, {
timeout: 30_000,
waitUntil: "domcontentloaded",
});
await this.waitForTableLoad();
}

public async setFilterValue(filterLabel: string, value: string): Promise<void> {
const filterPill = this.getFilterPill(filterLabel);

if ((await filterPill.count()) > 0) {
await filterPill.click();
}

// Wait for input to appear and fill it
const filterInput = this.page.locator(`input[placeholder*="${filterLabel}" i], input`).last();

await filterInput.waitFor({ state: "visible", timeout: 5000 });
await filterInput.fill(value);
await filterInput.press("Enter");
await this.waitForTableLoad();
}

public async verifyLogEntriesWithData(): Promise<void> {
const rows = await this.getEventLogRows();

if (rows.length === 0) {
throw new Error("No log entries found");
}

const [firstRow] = rows;

if (!firstRow) {
throw new Error("First row is undefined");
}

const whenCell = await this.getCellByColumnName(firstRow, "When");
const eventCell = await this.getCellByColumnName(firstRow, "Event");
const userCell = await this.getCellByColumnName(firstRow, "User");

const whenText = await whenCell.textContent();
const eventText = await eventCell.textContent();
const userText = await userCell.textContent();

expect(whenText?.trim()).toBeTruthy();
expect(eventText?.trim()).toBeTruthy();
expect(userText?.trim()).toBeTruthy();
}

public async verifyTableColumns(): Promise<void> {
const headers = await this.eventsTable.locator("thead th").allTextContents();
const expectedColumns = ["When", "Event", "User", "Extra"];

for (const col of expectedColumns) {
if (!headers.some((h) => h.toLowerCase().includes(col.toLowerCase()))) {
throw new Error(`Expected column "${col}" not found in headers: ${headers.join(", ")}`);
}
}
}

public async waitForEventsTable(): Promise<void> {
await this.waitForTableLoad();
}

/**
* Wait for table to finish loading
*/
Expand Down
Loading
Loading