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 10f691a92a0 Fix failing tests and remove Pagination and Sorting tests 
(#60738)
10f691a92a0 is described below

commit 10f691a92a0ff1150eabe38e1cfd27c9577ef458
Author: Vishakha Agrawal <[email protected]>
AuthorDate: Thu Feb 12 19:56:28 2026 +0530

    Fix failing tests and remove Pagination and Sorting tests (#60738)
---
 airflow-core/src/airflow/ui/playwright.config.ts   |   3 +
 .../airflow/ui/tests/e2e/pages/ConnectionsPage.ts  | 493 +++++++++++++++++++++
 .../airflow/ui/tests/e2e/specs/connections.spec.ts | 342 ++++++++++++++
 3 files changed, 838 insertions(+)

diff --git a/airflow-core/src/airflow/ui/playwright.config.ts 
b/airflow-core/src/airflow/ui/playwright.config.ts
index bd8f753620f..3e00ad21e59 100644
--- a/airflow-core/src/airflow/ui/playwright.config.ts
+++ b/airflow-core/src/airflow/ui/playwright.config.ts
@@ -24,6 +24,9 @@ export const testConfig = {
   asset: {
     name: process.env.TEST_ASSET_NAME ?? "s3://dag1/output_1.txt",
   },
+  connection: {
+    baseUrl: process.env.AIRFLOW_UI_BASE_URL ?? "http://localhost:28080";,
+  },
   credentials: {
     password: process.env.TEST_PASSWORD ?? "admin",
     username: process.env.TEST_USERNAME ?? "admin",
diff --git a/airflow-core/src/airflow/ui/tests/e2e/pages/ConnectionsPage.ts 
b/airflow-core/src/airflow/ui/tests/e2e/pages/ConnectionsPage.ts
new file mode 100644
index 00000000000..ebad0e1d2cf
--- /dev/null
+++ b/airflow-core/src/airflow/ui/tests/e2e/pages/ConnectionsPage.ts
@@ -0,0 +1,493 @@
+/*!
+ * 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 Locator, type Page } from "@playwright/test";
+import { BasePage } from "tests/e2e/pages/BasePage";
+
+type ConnectionDetails = {
+  conn_type: string;
+  connection_id: string;
+  description?: string;
+  extra?: string;
+  host?: string;
+  login?: string;
+  password?: string;
+  port?: number | string;
+  schema?: string;
+};
+
+export class ConnectionsPage extends BasePage {
+  // Page URLs
+  public static get connectionsListUrl(): string {
+    return "/connections";
+  }
+
+  public readonly addButton: Locator;
+  public readonly confirmDeleteButton: Locator;
+  public readonly connectionForm: Locator;
+  public readonly connectionIdHeader: Locator;
+  public readonly connectionIdInput: Locator;
+  // Core page elements
+  public readonly connectionsTable: Locator;
+  public readonly connectionTypeHeader: Locator;
+  public readonly connectionTypeSelect: Locator;
+  public readonly descriptionInput: Locator;
+  public readonly emptyState: Locator;
+  public readonly hostHeader: Locator;
+  public readonly hostInput: Locator;
+  public readonly loginInput: Locator;
+  public readonly passwordInput: Locator;
+
+  public readonly portInput: Locator;
+  public readonly rowsPerPageSelect: Locator;
+  public readonly saveButton: Locator;
+
+  public readonly schemaInput: Locator;
+  public readonly searchInput: Locator;
+  public readonly successAlert: Locator;
+  // Sorting and filtering
+  public readonly tableHeader: Locator;
+  public readonly testConnectionButton: Locator;
+
+  public constructor(page: Page) {
+    super(page);
+    // Table elements (Chakra UI DataTable)
+    this.connectionsTable = page.locator('[role="grid"], table');
+    this.emptyState = page.locator("text=/No connection found!/i");
+
+    // Action buttons
+    this.addButton = page.getByRole("button", { name: "Add Connection" });
+    this.testConnectionButton = page.locator('button:has-text("Test")');
+    this.saveButton = page.getByRole("button", { name: /^save$/i });
+
+    // Form inputs (Chakra UI inputs)
+    this.connectionForm = 
page.locator('[data-scope="dialog"][data-part="content"]');
+    this.connectionIdInput = 
page.locator('input[name="connection_id"]').first();
+    this.connectionTypeSelect = page.getByRole("combobox").first();
+    this.hostInput = page.locator('input[name="host"]').first();
+    this.portInput = page.locator('input[name="port"]').first();
+    this.loginInput = page.locator('input[name="login"]').first();
+    this.passwordInput = page.locator('input[name="password"], 
input[type="password"]').first();
+    this.schemaInput = page.locator('input[name="schema"]').first();
+    // Try multiple possible selectors
+    this.descriptionInput = page.locator('[name="description"]').first();
+
+    // Alerts
+    this.successAlert = page.locator('[data-scope="toast"][data-part="root"]');
+
+    // Delete confirmation dialog
+    this.confirmDeleteButton = 
page.locator('button:has-text("Delete")').first();
+    this.rowsPerPageSelect = page.locator("select");
+
+    // Sorting and filtering
+    this.tableHeader = page.locator('[role="columnheader"]').first();
+    this.connectionIdHeader = page.locator("th:has-text('Connection 
ID')").first();
+    this.connectionTypeHeader = page.locator('th:has-text("Connection 
Type")').first();
+    this.hostHeader = page.locator('th:has-text("Host")').first();
+    this.searchInput = page.locator('input[placeholder*="Search"], 
input[placeholder*="search"]').first();
+  }
+
+  // Click the Add button to create a new connection
+  public async clickAddButton(): Promise<void> {
+    await expect(this.addButton).toBeVisible({ timeout: 5000 });
+    await expect(this.addButton).toBeEnabled({ timeout: 5000 });
+    await this.addButton.click();
+    // Wait for form to load
+    await expect(this.connectionForm).toBeVisible({ timeout: 10_000 });
+  }
+
+  // Click edit button for a specific connection
+  public async clickEditButton(connectionId: string): Promise<void> {
+    const row = await this.findConnectionRow(connectionId);
+
+    if (!row) {
+      throw new Error(`Connection ${connectionId} not found`);
+    }
+
+    const editButton = row.getByRole("button", { name: "Edit Connection" });
+
+    await expect(editButton).toBeVisible({ timeout: 5000 });
+    await expect(editButton).toBeEnabled({ timeout: 5000 });
+    await editButton.click();
+    await expect(this.connectionForm).toBeVisible({ timeout: 10_000 });
+  }
+
+  // Check if a connection exists in the current view
+  public async connectionExists(connectionId: string): Promise<boolean> {
+    const emptyState = await this.page
+      .locator("text=No connection found!")
+      .isVisible({ timeout: 1000 })
+      .catch(() => false);
+
+    if (emptyState) {
+      return false;
+    }
+    const row = await this.findConnectionRow(connectionId);
+    const visible = row !== null;
+
+    return visible;
+  }
+
+  // Create a new connection with full workflow
+  public async createConnection(details: ConnectionDetails): Promise<void> {
+    await this.clickAddButton();
+    await this.fillConnectionForm(details);
+    await this.saveConnection();
+    await this.waitForConnectionsListLoad();
+  }
+
+  // Delete a connection by connection ID
+  public async deleteConnection(connectionId: string): Promise<void> {
+    // await this.navigate();
+    const row = await this.findConnectionRow(connectionId);
+
+    if (!row) {
+      throw new Error(`Connection ${connectionId} not found`);
+    }
+
+    // Find delete button in the row
+    await this.page.evaluate(() => {
+      const backdrops = 
document.querySelectorAll<HTMLElement>('[data-scope="dialog"][data-part="backdrop"]');
+
+      backdrops.forEach((backdrop) => {
+        const { state } = backdrop.dataset;
+
+        if (state === "closed") {
+          backdrop.remove();
+        }
+      });
+    });
+    const deleteButton = row.getByRole("button", { name: "Delete Connection" 
});
+
+    await expect(deleteButton).toBeVisible({ timeout: 10_000 });
+    await expect(deleteButton).toBeEnabled({ timeout: 5000 });
+    await deleteButton.click();
+
+    await expect(this.confirmDeleteButton).toBeVisible({ timeout: 10_000 });
+    await expect(this.confirmDeleteButton).toBeEnabled({ timeout: 5000 });
+    await this.confirmDeleteButton.click();
+
+    await expect(this.emptyState).toBeVisible({ timeout: 5000 });
+  }
+
+  // Edit a connection by connection ID
+  public async editConnection(connectionId: string, updates: 
Partial<ConnectionDetails>): Promise<void> {
+    const row = await this.findConnectionRow(connectionId);
+
+    if (!row) {
+      throw new Error(`Connection ${connectionId} not found`);
+    }
+
+    await this.clickEditButton(connectionId);
+
+    // Wait for form to load
+    await expect(this.connectionIdInput).toBeVisible({ timeout: 10_000 });
+
+    // Fill the fields that need updating
+    await this.fillConnectionForm(updates);
+    await this.saveConnection();
+  }
+
+  // Fill connection form with details
+  public async fillConnectionForm(details: Partial<ConnectionDetails>): 
Promise<void> {
+    if (details.connection_id !== undefined && details.connection_id !== "") {
+      await this.connectionIdInput.fill(details.connection_id);
+    }
+
+    if (details.conn_type !== undefined && details.conn_type !== "") {
+      // Click the select field to open the dropdown
+      const selectCombobox = this.page.getByRole("combobox").first();
+
+      await expect(selectCombobox).toBeEnabled({ timeout: 25_000 });
+
+      await selectCombobox.click({ timeout: 3000 });
+
+      // Wait for options to appear and click the matching option
+      const option = this.page.getByRole("option", { name: new 
RegExp(details.conn_type, "i") }).first();
+
+      await option.click({ timeout: 2000 }).catch(() => {
+        // If option click fails, try typing in the input
+        if (details.conn_type !== undefined && details.conn_type !== "") {
+          void this.page.keyboard.type(details.conn_type);
+        }
+      });
+    }
+
+    if (details.host !== undefined && details.host !== "") {
+      await expect(this.hostInput).toBeVisible({ timeout: 10_000 });
+      await this.hostInput.fill(details.host);
+    }
+
+    if (details.port !== undefined && details.port !== "") {
+      await expect(this.portInput).toBeVisible({ timeout: 10_000 });
+      await this.portInput.fill(String(details.port));
+    }
+
+    if (details.login !== undefined && details.login !== "") {
+      await expect(this.loginInput).toBeVisible({ timeout: 10_000 });
+      await this.loginInput.fill(details.login);
+    }
+
+    if (details.password !== undefined && details.password !== "") {
+      await expect(this.passwordInput).toBeVisible({ timeout: 10_000 });
+      await this.passwordInput.fill(details.password);
+    }
+
+    if (details.description !== undefined && details.description !== "") {
+      await expect(this.descriptionInput).toBeVisible({ timeout: 10_000 });
+      await this.descriptionInput.fill(details.description);
+    }
+
+    if (details.schema !== undefined && details.schema !== "") {
+      await expect(this.schemaInput).toBeVisible({ timeout: 10_000 });
+      await this.schemaInput.fill(details.schema);
+    }
+
+    if (details.extra !== undefined && details.extra !== "") {
+      const extraAccordion = this.page.locator('button:has-text("Extra Fields 
JSON")').first();
+      const accordionVisible = await extraAccordion.isVisible({ timeout: 5000 
}).catch(() => false);
+
+      if (accordionVisible) {
+        await extraAccordion.click();
+        const extraEditor = 
this.page.locator('.cm-content[contenteditable="true"]:visible').first();
+
+        await extraEditor.waitFor({ state: "visible", timeout: 5000 });
+        await extraEditor.clear();
+        await extraEditor.fill(details.extra);
+        await extraEditor.blur();
+      }
+    }
+  }
+
+  // Get connection count from current page
+  public async getConnectionCount(): Promise<number> {
+    const ids = await this.getConnectionIds();
+
+    return ids.length;
+  }
+
+  // Get all connection IDs from the current page
+  public async getConnectionIds(): Promise<Array<string>> {
+    await expect(this.page.locator("tbody tr").first()).toBeVisible({ timeout: 
5000 });
+
+    let stableRowCount = 0;
+
+    await expect
+      .poll(
+        async () => {
+          const count1 = await this.page.locator("tbody tr").count();
+
+          await this.page.evaluate(() => new Promise((r) => setTimeout(r, 
200)));
+          const count2 = await this.page.locator("tbody tr").count();
+
+          if (count1 === count2 && count1 > 0) {
+            stableRowCount = count1;
+
+            return true;
+          }
+
+          return false;
+        },
+        { intervals: [100, 200, 500], timeout: 10_000 },
+      )
+      .toBeTruthy()
+      .catch(() => {
+        // If timeout, just use current count
+        stableRowCount = 0;
+      });
+
+    if (stableRowCount === 0) {
+      return [];
+    }
+
+    let rows = this.page.locator("tbody tr");
+    const connectionIds: Array<string> = [];
+
+    // Process all rows
+    for (let i = 0; i < stableRowCount; i++) {
+      try {
+        const row = rows.nth(i);
+        const cells = row.locator("td");
+        const cellCount = await cells.count();
+
+        if (cellCount > 1) {
+          // Connection ID is typically in the second cell (after checkbox)
+          const idCell = cells.nth(1);
+          const text = await idCell.textContent({ timeout: 3000 });
+
+          if (text !== null && text.trim() !== "") {
+            connectionIds.push(text.trim());
+          }
+        }
+      } catch {
+        // Skip rows that can't be read
+        continue;
+      }
+    }
+
+    return connectionIds;
+  }
+
+  // Navigate to Connections list page
+  public async navigate(): Promise<void> {
+    await this.navigateTo(ConnectionsPage.connectionsListUrl);
+    await this.waitForConnectionsListLoad();
+  }
+
+  // Save the connection form
+  public async saveConnection(): Promise<void> {
+    await expect(this.saveButton).toBeVisible({ timeout: 10_000 });
+    await expect(this.saveButton).toBeEnabled({ timeout: 5000 });
+    await this.saveButton.click();
+
+    // Wait for either redirect OR success message
+    await Promise.race([
+      this.page.waitForURL("**/connections", { timeout: 10_000 }),
+      this.successAlert.waitFor({ state: "visible", timeout: 10_000 }),
+    ]);
+  }
+
+  // Search for connections using the search input
+  public async searchConnections(searchTerm: string): Promise<void> {
+    await (searchTerm === "" ? this.searchInput.clear() : 
this.searchInput.fill(searchTerm));
+
+    // Wait for search to complete by checking results stability
+    await expect
+      .poll(
+        async () => {
+          const ids = await this.getConnectionIds();
+
+          // If we expect no results
+          const isEmptyVisible = await this.emptyState.isVisible().catch(() => 
false);
+
+          if (isEmptyVisible) {
+            return ids.length === 0;
+          }
+
+          // If we expect results, verify they match the search term
+          if (ids.length === 0) {
+            return false; // Still loading
+          }
+
+          if (searchTerm === "") {
+            // Get count twice to ensure it's stable
+            const count1 = ids.length;
+
+            await this.page.evaluate(() => new Promise((r) => setTimeout(r, 
200)));
+            const count2 = await this.getConnectionIds().then((allIds) => 
allIds.length);
+
+            // Stable when count doesn't change
+            return count1 === count2 && count1 > 0;
+          }
+
+          // All visible IDs should contain the search term (case-insensitive)
+          return ids.every((id) => 
id.toLowerCase().includes(searchTerm.toLowerCase()));
+        },
+        { message: "Search results did not match search term", timeout: 20_000 
},
+      )
+      .toBeTruthy();
+  }
+
+  // Verify connection details are displayed in the list
+  public async verifyConnectionInList(connectionId: string, expectedType: 
string): Promise<void> {
+    const row = await this.findConnectionRow(connectionId);
+
+    if (!row) {
+      throw new Error(`Connection ${connectionId} not found in list`);
+    }
+
+    const rowText = await row.textContent();
+
+    expect(rowText).toContain(connectionId);
+    expect(rowText).toContain(expectedType);
+  }
+
+  private async findConnectionRow(connectionId: string): Promise<Locator | 
null> {
+    // Try search first (faster)
+    const hasSearch = await this.searchInput.isVisible({ timeout: 500 
}).catch(() => false);
+
+    if (hasSearch) {
+      return await this.findConnectionRowUsingSearch(connectionId);
+    }
+
+    return null;
+  }
+
+  private async findConnectionRowUsingSearch(connectionId: string): 
Promise<Locator | null> {
+    await this.searchConnections(connectionId);
+
+    // Check if table is visible (without throwing)
+    const isTableVisible = await this.connectionsTable.isVisible({ timeout: 
5000 }).catch(() => false);
+
+    if (!isTableVisible) {
+      return null;
+    }
+
+    const row = this.page.locator("tbody tr").filter({ hasText: connectionId 
}).first();
+
+    const rowExists = await row.isVisible({ timeout: 3000 }).catch(() => 
false);
+
+    if (!rowExists) {
+      return null;
+    }
+
+    return row;
+  }
+
+  // Wait for connections list to fully load
+  private async waitForConnectionsListLoad(): Promise<void> {
+    await expect(this.page).toHaveURL(/\/connections/, { timeout: 3000 });
+    await this.page.waitForLoadState("domcontentloaded");
+
+    const table = this.connectionsTable;
+
+    // Wait for either table or empty state
+    await expect(table.or(this.emptyState)).toBeVisible({ timeout: 10_000 });
+
+    // If table exists, wait for rows
+    if (await table.isVisible().catch(() => false)) {
+      await this.page
+        .locator("tbody tr")
+        .first()
+        .waitFor({ state: "visible", timeout: 10_000 })
+        .catch(() => {
+          // No rows found
+        });
+
+      // Wait for row count to stabilize
+      await expect
+        .poll(
+          async () => {
+            const count1 = await this.page.locator("tbody tr").count();
+
+            if (count1 === 0) return true;
+
+            await this.page.evaluate(() => new Promise((r) => setTimeout(r, 
300)));
+            const count2 = await this.page.locator("tbody tr").count();
+
+            return count1 === count2;
+          },
+          { timeout: 15_000 },
+        )
+        .toBeTruthy()
+        .catch(() => {
+          // Timeout - proceed anyway
+        });
+    }
+  }
+}
diff --git a/airflow-core/src/airflow/ui/tests/e2e/specs/connections.spec.ts 
b/airflow-core/src/airflow/ui/tests/e2e/specs/connections.spec.ts
new file mode 100644
index 00000000000..9d2493f8aab
--- /dev/null
+++ b/airflow-core/src/airflow/ui/tests/e2e/specs/connections.spec.ts
@@ -0,0 +1,342 @@
+/*!
+ * 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 { testConfig, AUTH_FILE } from "playwright.config";
+import { ConnectionsPage } from "tests/e2e/pages/ConnectionsPage";
+
+test.describe("Connections Page - List and Display", () => {
+  let connectionsPage: ConnectionsPage;
+  const { baseUrl } = testConfig.connection;
+  const timestamp = Date.now();
+  const seedConnection = {
+    conn_type: "http",
+    connection_id: `list_seed_conn_${timestamp}`,
+    host: "seed.example.com",
+  };
+
+  test.beforeEach(({ page }) => {
+    connectionsPage = new ConnectionsPage(page);
+  });
+
+  test.beforeAll(async ({ browser }) => {
+    const context = await browser.newContext({ storageState: AUTH_FILE });
+    const page = await context.newPage();
+
+    const response = await page.request.post(`${baseUrl}/api/v2/connections`, {
+      data: seedConnection,
+      headers: { "Content-Type": "application/json" },
+    });
+
+    expect([200, 201, 409]).toContain(response.status());
+    await context.close();
+  });
+
+  test.afterAll(async ({ browser }) => {
+    const context = await browser.newContext({ storageState: AUTH_FILE });
+    const page = await context.newPage();
+
+    const response = await 
page.request.delete(`${baseUrl}/api/v2/connections/list_seed_conn_${timestamp}`);
+
+    expect([204, 404]).toContain(response.status());
+    await context.close();
+  });
+
+  test("should display connections list page", async () => {
+    await connectionsPage.navigate();
+
+    // Verify the page is loaded
+    expect(connectionsPage.page.url()).toContain("/connections");
+
+    // Verify table or list is visible
+    await expect(connectionsPage.connectionsTable).toBeVisible();
+  });
+
+  test("should display connections with correct columns", async () => {
+    await connectionsPage.navigate();
+
+    // Check that we have at least one row
+    const count = await connectionsPage.getConnectionCount();
+
+    expect(count).toBeGreaterThan(0);
+
+    // Verify column headers exist
+    await expect(connectionsPage.connectionIdHeader).toBeVisible();
+    await expect(connectionsPage.connectionTypeHeader).toBeVisible();
+    await expect(connectionsPage.hostHeader).toBeVisible();
+
+    // Verify the seed connection is displayed in the list
+    await connectionsPage.verifyConnectionInList(seedConnection.connection_id, 
seedConnection.conn_type);
+  });
+
+  test("should have Add button visible", async () => {
+    await connectionsPage.navigate();
+    await expect(connectionsPage.addButton).toBeVisible();
+  });
+});
+
+test.describe("Connections Page - CRUD Operations", () => {
+  let connectionsPage: ConnectionsPage;
+  const { baseUrl } = testConfig.connection;
+  const timestamp = Date.now();
+
+  // Connection created via API in beforeAll - used for edit and display tests
+  const existingConnection = {
+    conn_type: "postgres",
+    connection_id: `existing_conn_${timestamp}`,
+    host: `existing-host-${timestamp}.example.com`,
+    login: `existing_user_${timestamp}`,
+  };
+
+  const updatedConnection = {
+    conn_type: "postgres",
+    description: `Updated test connection at ${new Date().toISOString()}`,
+    host: `updated-host-${timestamp}.example.com`,
+    login: `updated_user_${timestamp}`,
+    port: 5433,
+  };
+
+  // Connection created via UI in test - used for create and delete tests
+  const newConnection = {
+    conn_type: "postgres",
+    connection_id: `new_conn_${timestamp}`,
+    description: `Test connection created at ${new Date().toISOString()}`,
+    extra: JSON.stringify({
+      options: "-c statement_timeout=5000",
+      sslmode: "require",
+    }),
+    host: `new-host-${timestamp}.example.com`,
+    login: `new_user_${timestamp}`,
+    password: `new_password_${timestamp}`,
+    port: 5432,
+    schema: "test_db",
+  };
+
+  test.beforeEach(({ page }) => {
+    connectionsPage = new ConnectionsPage(page);
+  });
+
+  test.beforeAll(async ({ browser }) => {
+    // Create existing connection via API for edit and display tests
+    const context = await browser.newContext({ storageState: AUTH_FILE });
+    const page = await context.newPage();
+
+    await page.request.post(`${baseUrl}/api/v2/connections`, {
+      data: existingConnection,
+      headers: { "Content-Type": "application/json" },
+    });
+
+    await context.close();
+  });
+
+  test.afterAll(async ({ browser }) => {
+    // Cleanup all test connections via API
+    const context = await browser.newContext({ storageState: AUTH_FILE });
+    const page = await context.newPage();
+
+    for (const connId of [
+      existingConnection.connection_id,
+      newConnection.connection_id,
+      `temp_conn_${timestamp}_delete`,
+    ]) {
+      await page.request.delete(`${baseUrl}/api/v2/connections/${connId}`);
+    }
+
+    await context.close();
+  });
+
+  test("should create a new connection and display it in list", async () => {
+    test.setTimeout(120_000);
+    await connectionsPage.navigate();
+
+    // Create connection via UI
+    await connectionsPage.createConnection(newConnection);
+    const exists = await 
connectionsPage.connectionExists(newConnection.connection_id);
+
+    expect(exists).toBeTruthy();
+    // Verify it appears in the list with correct type
+    await connectionsPage.verifyConnectionInList(newConnection.connection_id, 
newConnection.conn_type);
+  });
+
+  test("should edit an existing connection", async () => {
+    test.setTimeout(120_000);
+    await connectionsPage.navigate();
+
+    // Verify connection exists before editing (created in beforeAll)
+    const exists = await 
connectionsPage.connectionExists(existingConnection.connection_id);
+
+    expect(exists).toBeTruthy();
+
+    // Edit the connection
+    await connectionsPage.editConnection(existingConnection.connection_id, 
updatedConnection);
+
+    // Verify the connection still exists after editing
+    const stillExists = await 
connectionsPage.connectionExists(existingConnection.connection_id);
+
+    expect(stillExists).toBeTruthy();
+  });
+
+  test("should delete a connection", async () => {
+    test.setTimeout(120_000);
+
+    // Create a temporary connection for deletion test
+    const tempConnection = {
+      conn_type: "postgres",
+      connection_id: `temp_conn_${timestamp}_delete`,
+      host: `temp-host-${timestamp}.example.com`,
+      login: "temp_user",
+      password: "temp_password",
+    };
+
+    await connectionsPage.navigate();
+    await connectionsPage.createConnection(tempConnection);
+
+    const exists = await 
connectionsPage.connectionExists(tempConnection.connection_id);
+
+    expect(exists).toBeTruthy();
+
+    // Delete the connection
+    await connectionsPage.deleteConnection(tempConnection.connection_id);
+
+    const stillExists = await 
connectionsPage.connectionExists(tempConnection.connection_id);
+
+    expect(stillExists).toBeFalsy();
+  });
+});
+
+test.describe("Connections Page - Search and Filter", () => {
+  let connectionsPage: ConnectionsPage;
+  const { baseUrl } = testConfig.connection;
+  const timestamp = Date.now();
+
+  const searchTestConnections = [
+    {
+      conn_type: "postgres",
+      connection_id: `search_production_${timestamp}`,
+      host: "prod-db.example.com",
+      login: "prod_user",
+    },
+    {
+      conn_type: "mysql",
+      connection_id: `search_staging_${timestamp}`,
+      host: "staging-db.example.com",
+      login: "staging_user",
+    },
+    {
+      conn_type: "http",
+      connection_id: `search_development_${timestamp}`,
+      host: "dev-api.example.com",
+      login: "dev_user",
+    },
+  ];
+
+  test.beforeEach(({ page }) => {
+    connectionsPage = new ConnectionsPage(page);
+  });
+
+  test.beforeAll(async ({ browser }) => {
+    // Create test connections
+    const context = await browser.newContext({ storageState: AUTH_FILE });
+    const page = await context.newPage();
+
+    for (const conn of searchTestConnections) {
+      const response = await 
page.request.post(`${baseUrl}/api/v2/connections`, {
+        data: JSON.stringify(conn),
+        headers: {
+          "Content-Type": "application/json",
+        },
+      });
+
+      expect([200, 201, 409]).toContain(response.status());
+    }
+  });
+
+  test.afterAll(async ({ browser }) => {
+    // Cleanup
+    const context = await browser.newContext({ storageState: AUTH_FILE });
+    const page = await context.newPage();
+
+    for (const conn of searchTestConnections) {
+      const response = await 
page.request.delete(`${baseUrl}/api/v2/connections/${conn.connection_id}`);
+
+      expect([204, 404]).toContain(response.status());
+    }
+  });
+
+  test("should filter connections by search term", async () => {
+    await connectionsPage.navigate();
+
+    const initialCount = await connectionsPage.getConnectionCount();
+
+    expect(initialCount).toBeGreaterThan(0);
+
+    const searchTerm = "production";
+
+    await connectionsPage.searchConnections(searchTerm);
+
+    await expect
+      .poll(
+        async () => {
+          const ids = await connectionsPage.getConnectionIds();
+
+          // Verify we have results AND they match the search term
+          return ids.length > 0 && ids.every((id) => 
id.toLowerCase().includes(searchTerm.toLowerCase()));
+        },
+        { intervals: [500], timeout: 10_000 },
+      )
+      .toBe(true);
+
+    const filteredIds = await connectionsPage.getConnectionIds();
+
+    expect(filteredIds.length).toBeGreaterThan(0);
+    for (const id of filteredIds) {
+      expect(id.toLowerCase()).toContain(searchTerm.toLowerCase());
+    }
+  });
+
+  test("should display all connections when search is cleared", async () => {
+    test.setTimeout(120_000);
+    await connectionsPage.navigate();
+
+    const initialCount = await connectionsPage.getConnectionCount();
+
+    expect(initialCount).toBeGreaterThan(0);
+
+    // Search for something
+    await connectionsPage.searchConnections("production");
+
+    // Wait for search results
+    await expect
+      .poll(
+        async () => {
+          const count = await connectionsPage.getConnectionCount();
+
+          return count > 0; // Just verify we have some results
+        },
+        { intervals: [500], timeout: 10_000 },
+      )
+      .toBe(true);
+
+    // Clear search
+    await connectionsPage.searchConnections("");
+
+    const finalCount = await connectionsPage.getConnectionCount();
+
+    expect(finalCount).toBeGreaterThanOrEqual(initialCount);
+  });
+});

Reply via email to