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

Code before:
  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();
    this.eventTypeColumn = page.locator("thead th").nth(1);
    this.userColumn = page.locator("thead th").nth(2);
    this.extraColumn = page.locator("thead th").nth(3);
    this.dagIdColumn = page.locator("thead th").nth(4);
    this.runIdColumn = page.locator("thead th").nth(5);
    this.taskIdColumn = page.locator("thead th").nth(6);
    this.mapIndexColumn = page.locator("thead th").nth(7);
    this.tryNumberColumn = page.locator("thead th").nth(8);
    this.paginationNextButton = page.locator('[data-testid="next"]');
    this.paginationPrevButton = page.locator('[data-testid="prev"]');
  }

  /**
   * Click on a column header to sort
   */
  public async clickColumnHeader(columnIndex: number): Promise<void> {
    const header = this.eventsTable.locator(`thead th:nth-child(${columnIndex + 1})`);

    await header.click();
    await this.waitForEventsTable();
  }

  /**
   * Click next page button
   */

Review comment (suggestion):
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 });
```

Code after:
  public readonly paginationPrevButton: Locator;
  public readonly tableRows: Locator;
  public readonly whenColumn: Locator;

  private currentDagId?: string;
  private currentLimit?: number;

  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"]');
    this.tableRows = this.eventsTable.locator("tbody tr");
    this.whenColumn = this.eventsTable.locator('th:has-text("When")');
  }

  public static getEventsUrl(dagId: string): string {
    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> {