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

Code before:
 * under the License.
 */
import { expect, type Locator, type Page } from "@playwright/test";
import { BasePage } from "tests/e2e/pages/BasePage";

export class GridPage extends BasePage {
  public readonly gridCells: Locator;
  public readonly gridViewButton: Locator;
  public readonly taskNameLinks: Locator;

  public constructor(page: Page) {
    super(page);
    this.gridViewButton = page.locator('button[title*="Grid"], button[title*="grid"]').first();
    this.gridCells = page.locator('a[id^="grid-"]');
    this.taskNameLinks = page.locator('a[href*="/tasks/"]');
  }

  public async clickGridCellAndVerifyDetails(): Promise<void> {
    await this.waitForGridToLoad();

    const firstCell = this.gridCells.first();

    await expect(firstCell).toBeVisible();
    await firstCell.click();
    await this.page.waitForURL(/.*\/tasks\/.*/, { timeout: 15_000 });
    await this.page.waitForLoadState("networkidle");
  }

  public getGridCellByTaskId(taskId: string): Locator {
    return this.page.locator(`a[id$="-${taskId}"]`);
  }

  public async getGridCellCount(): Promise<number> {
    await this.waitForGridToLoad();

    return this.gridCells.count();
  }

  public async getTaskNames(): Promise<Array<string>> {
    await this.waitForGridToLoad();

    const names = await this.taskNameLinks.allTextContents();
    const uniqueNames = [...new Set(names.map((name) => name.trim()).filter((name) => name !== ""))];

    return uniqueNames;
  }

  public async navigateToDag(dagId: string): Promise<void> {
    await this.navigateTo(`/dags/${dagId}`);
    await this.page.waitForURL(`**/dags/${dagId}**`, { timeout: 15_000 });
    await this.page.waitForLoadState("networkidle");

Review comment (suggestion):
This can be unreliable. Consider replacing with explicit element waits (which you already do with waitForGridToLoad()).

Code after:
 * under the License.
 */
import { expect, type Locator, type Page } from "@playwright/test";
import { BasePage } from "tests/e2e/pages/BasePage";

export class GridPage extends BasePage {
  public readonly gridCells: Locator;
  public readonly gridViewButton: Locator;
  public readonly taskNameLinks: Locator;

  public constructor(page: Page) {
    super(page);
    this.gridViewButton = page.getByTestId("grid-view-button");
    this.gridCells = page.locator('a[id^="grid-"]');
    this.taskNameLinks = page.locator('a[href*="/tasks/"]');
  }

  public async clickGridCellAndVerifyDetails(): Promise<void> {
    await this.waitForGridToLoad();

    const firstCell = this.gridCells.first();

    await expect(firstCell).toBeVisible();
    await firstCell.click();
    await this.page.waitForURL(/.*\/tasks\/.*/, { timeout: 15_000 });
    await expect(this.page.getByTestId("virtualized-list")).toBeVisible({ timeout: 10_000 });
  }

  public async getGridCellCount(): Promise<number> {
    await this.waitForGridToLoad();

    return this.gridCells.count();
  }

  public async getTaskNames(): Promise<Array<string>> {
    await this.waitForGridToLoad();

    const names = await this.taskNameLinks.allTextContents();
    const uniqueNames = [...new Set(names.map((name) => name.trim()).filter((name) => name !== ""))];

    return uniqueNames;
  }

  public async navigateToDag(dagId: string): Promise<void> {
    await this.navigateTo(`/dags/${dagId}`);
    await this.page.waitForURL(`**/dags/${dagId}**`, { timeout: 15_000 });
    await expect(this.gridViewButton).toBeVisible({ timeout: 10_000 });
  }

  public async switchToGridView(): Promise<void> {
    await expect(this.gridViewButton).toBeVisible({ timeout: 10_000 });