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 323bc16c9b9 Test(UI): Add E2E tests for Asset Details (#59939)
323bc16c9b9 is described below
commit 323bc16c9b923c325c436a86a9f59af30833a987
Author: M Junaid Shaukat <[email protected]>
AuthorDate: Wed Jan 28 09:49:57 2026 +0500
Test(UI): Add E2E tests for Asset Details (#59939)
Add E2E tests for Asset Details
Signed-off-by: junaiddshaukat <[email protected]>
Co-authored-by: Rahul Vats <[email protected]>
---
airflow-core/src/airflow/ui/playwright.config.ts | 3 +
.../airflow/ui/tests/e2e/pages/AssetDetailPage.ts | 82 ++++++++++++++++++++++
.../src/airflow/ui/tests/e2e/specs/asset.spec.ts | 20 +++++-
3 files changed, 103 insertions(+), 2 deletions(-)
diff --git a/airflow-core/src/airflow/ui/playwright.config.ts
b/airflow-core/src/airflow/ui/playwright.config.ts
index 3b2d8d7d103..bd8f753620f 100644
--- a/airflow-core/src/airflow/ui/playwright.config.ts
+++ b/airflow-core/src/airflow/ui/playwright.config.ts
@@ -21,6 +21,9 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
export const testConfig = {
+ asset: {
+ name: process.env.TEST_ASSET_NAME ?? "s3://dag1/output_1.txt",
+ },
credentials: {
password: process.env.TEST_PASSWORD ?? "admin",
username: process.env.TEST_USERNAME ?? "admin",
diff --git a/airflow-core/src/airflow/ui/tests/e2e/pages/AssetDetailPage.ts
b/airflow-core/src/airflow/ui/tests/e2e/pages/AssetDetailPage.ts
new file mode 100644
index 00000000000..9fd8b6f23f6
--- /dev/null
+++ b/airflow-core/src/airflow/ui/tests/e2e/pages/AssetDetailPage.ts
@@ -0,0 +1,82 @@
+/*!
+ * 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 Page } from "@playwright/test";
+
+import { BasePage } from "./BasePage";
+
+export class AssetDetailPage extends BasePage {
+ public static get url(): string {
+ return "/assets";
+ }
+
+ public constructor(page: Page) {
+ super(page);
+ }
+
+ public async clickOnAsset(name: string): Promise<void> {
+ await this.page.getByRole("link", { exact: true, name }).click();
+ }
+
+ public async goto(): Promise<void> {
+ await this.navigateTo(AssetDetailPage.url);
+ }
+
+ public async verifyAssetDetails(name: string): Promise<void> {
+ await expect(this.page.getByRole("heading", { name })).toBeVisible();
+ }
+
+ public async verifyProducingTasks(minCount: number): Promise<void> {
+ await this.verifyStatSection("Producing Tasks", minCount);
+ }
+
+ public async verifyScheduledDags(minCount: number): Promise<void> {
+ await this.verifyStatSection("Scheduled Dags", minCount);
+ }
+
+ /**
+ * Common helper to verify stat sections (Producing Tasks, Scheduled Dags)
+ * Uses stable selectors based on text content and ARIA roles
+ */
+ private async verifyStatSection(labelText: string, minCount: number):
Promise<void> {
+ const label = this.page.getByText(labelText, { exact: true });
+
+ await expect(label).toBeVisible();
+
+ // Find the button that follows the label in the same stat section
+ // Navigate to the label's parent container and find the first button
within it
+ const statContainer = label.locator("xpath=./parent::*");
+ const button = statContainer.getByRole("button").first();
+
+ await expect(button).toBeVisible();
+ const text = await button.textContent();
+ const count = parseInt(text?.split(" ")[0] ?? "0", 10);
+
+ expect(count).toBeGreaterThanOrEqual(minCount);
+
+ if (count > 0) {
+ await button.click();
+ // Wait for popover and verify links using role-based selector
+ const popoverLinks = this.page.getByRole("dialog").getByRole("link");
+
+ await expect(popoverLinks).toHaveCount(count);
+ // Close popover
+ await this.page.keyboard.press("Escape");
+ }
+ }
+}
diff --git a/airflow-core/src/airflow/ui/tests/e2e/specs/asset.spec.ts
b/airflow-core/src/airflow/ui/tests/e2e/specs/asset.spec.ts
index e1f05790fe9..a123b64b101 100644
--- a/airflow-core/src/airflow/ui/tests/e2e/specs/asset.spec.ts
+++ b/airflow-core/src/airflow/ui/tests/e2e/specs/asset.spec.ts
@@ -17,8 +17,9 @@
* under the License.
*/
import { expect, test } from "@playwright/test";
-import { AUTH_FILE } from "playwright.config";
+import { AUTH_FILE, testConfig } from "playwright.config";
+import { AssetDetailPage } from "../pages/AssetDetailPage";
import { AssetListPage } from "../pages/AssetListPage";
import { DagsPage } from "../pages/DagsPage";
@@ -88,7 +89,7 @@ test.describe("Assets Page", () => {
expect(initialCount).toBeGreaterThan(0);
- const searchTerm = "s3://dag1/output_1.txt";
+ const searchTerm = testConfig.asset.name;
await assets.searchInput.fill(searchTerm);
@@ -135,4 +136,19 @@ test.describe("Assets Page", () => {
await expect.poll(() => assets.assetNames(), { timeout: 30_000
}).not.toEqual(page2Assets);
});
+
+ test("verify asset details and dependencies", async ({ page }) => {
+ const assetDetailPage = new AssetDetailPage(page);
+ const assetName = testConfig.asset.name;
+
+ await assetDetailPage.goto();
+
+ await assetDetailPage.clickOnAsset(assetName);
+
+ await assetDetailPage.verifyAssetDetails(assetName);
+
+ await assetDetailPage.verifyProducingTasks(1);
+
+ await assetDetailPage.verifyScheduledDags(1);
+ });
});