This is an automated email from the ASF dual-hosted git repository.

rahulvats pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new b6bb1552e55 Add E2E tests for Plugins page (#61057)
b6bb1552e55 is described below

commit b6bb1552e55fbf5761d45605dc65e27d434a53b2
Author: Eason09053360 <[email protected]>
AuthorDate: Sat Feb 21 17:46:05 2026 +0800

    Add E2E tests for Plugins page (#61057)
    
    Add E2E tests for Plugins page
---
 .../src/airflow/ui/tests/e2e/pages/PluginsPage.ts  | 90 ++++++++++++++++++++++
 .../src/airflow/ui/tests/e2e/specs/plugins.spec.ts | 72 +++++++++++++++++
 2 files changed, 162 insertions(+)

diff --git a/airflow-core/src/airflow/ui/tests/e2e/pages/PluginsPage.ts 
b/airflow-core/src/airflow/ui/tests/e2e/pages/PluginsPage.ts
new file mode 100644
index 00000000000..c8f66309e57
--- /dev/null
+++ b/airflow-core/src/airflow/ui/tests/e2e/pages/PluginsPage.ts
@@ -0,0 +1,90 @@
+/*!
+ * 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 "./BasePage";
+
+export class PluginsPage extends BasePage {
+  public readonly heading: Locator;
+  public readonly rows: Locator;
+  public readonly table: Locator;
+
+  public constructor(page: Page) {
+    super(page);
+
+    this.heading = page.getByRole("heading", {
+      name: /plugins/i,
+    });
+    this.table = page.getByTestId("table-list");
+    this.rows = this.table.locator("tbody tr").filter({
+      has: page.locator("td"),
+    });
+  }
+
+  public async getPluginCount(): Promise<number> {
+    return this.rows.count();
+  }
+
+  public async getPluginNames(): Promise<Array<string>> {
+    const count = await this.rows.count();
+
+    if (count === 0) {
+      return [];
+    }
+
+    return this.rows.locator("td:first-child").allTextContents();
+  }
+
+  public async getPluginSources(): Promise<Array<string>> {
+    const count = await this.rows.count();
+
+    if (count === 0) {
+      return [];
+    }
+
+    return this.rows.locator("td:nth-child(2)").allTextContents();
+  }
+
+  public async navigate(): Promise<void> {
+    await this.navigateTo("/plugins");
+  }
+
+  public async waitForLoad(): Promise<void> {
+    await this.table.waitFor({ state: "visible", timeout: 30_000 });
+    await this.waitForTableData();
+  }
+
+  public async waitForTableData(): Promise<void> {
+    await this.page.waitForFunction(
+      () => {
+        const table = document.querySelector('[data-testid="table-list"]');
+
+        if (!table) {
+          return false;
+        }
+
+        const cells = table.querySelectorAll("tbody tr td");
+
+        return cells.length > 0;
+      },
+      undefined,
+      { timeout: 30_000 },
+    );
+  }
+}
diff --git a/airflow-core/src/airflow/ui/tests/e2e/specs/plugins.spec.ts 
b/airflow-core/src/airflow/ui/tests/e2e/specs/plugins.spec.ts
new file mode 100644
index 00000000000..6d502cff603
--- /dev/null
+++ b/airflow-core/src/airflow/ui/tests/e2e/specs/plugins.spec.ts
@@ -0,0 +1,72 @@
+/*!
+ * 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 { PluginsPage } from "../pages/PluginsPage";
+
+test.describe("Plugins Page", () => {
+  let pluginsPage: PluginsPage;
+
+  test.beforeEach(async ({ page }) => {
+    pluginsPage = new PluginsPage(page);
+    await pluginsPage.navigate();
+    await pluginsPage.waitForLoad();
+  });
+
+  test("verify plugins page heading is visible", async () => {
+    await expect(pluginsPage.heading).toBeVisible();
+  });
+
+  test("verify plugins table is visible", async () => {
+    await expect(pluginsPage.table).toBeVisible();
+  });
+
+  test("verify plugins list displays with data", async () => {
+    const count = await pluginsPage.getPluginCount();
+
+    expect(count).toBeGreaterThan(0);
+  });
+
+  test("verify each plugin has a name", async () => {
+    const pluginNames = await pluginsPage.getPluginNames();
+
+    expect(pluginNames.length).toBeGreaterThan(0);
+
+    for (const name of pluginNames) {
+      expect(name.trim().length).toBeGreaterThan(0);
+    }
+  });
+
+  test("verify each plugin has a source", async () => {
+    const pluginSources = await pluginsPage.getPluginSources();
+
+    expect(pluginSources.length).toBeGreaterThan(0);
+
+    for (const source of pluginSources) {
+      expect(source.trim().length).toBeGreaterThan(0);
+    }
+  });
+
+  test("verify plugin names and sources have matching counts", async () => {
+    const pluginNames = await pluginsPage.getPluginNames();
+    const pluginSources = await pluginsPage.getPluginSources();
+
+    expect(pluginNames.length).toBe(pluginSources.length);
+  });
+});

Reply via email to