Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .secrets.baseline

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7273,6 +7273,10 @@ PLAYWRIGHT_SCREENSHOTS := $(PLAYWRIGHT_DIR)/screenshots
PLAYWRIGHT_VIDEOS := $(PLAYWRIGHT_DIR)/videos
PLAYWRIGHT_SLOWMO ?= 750
TEST_BASE_URL ?= http://localhost:8080
# Auth env for Playwright runs against the docker-compose stack: must match
# the gateway's signing secret and bootstrap admin password (docker-compose.yml).
JWT_SECRET_KEY ?= my-test-key-but-now-longer-than-32-bytes
PLATFORM_ADMIN_PASSWORD ?= changeme
ZAP_BASE_URL ?= http://localhost:8090
ZAP_API_KEY ?= changeme
# URL ZAP uses internally to spider the app. nginx exposes port 80 on mcpnet
Expand Down Expand Up @@ -7348,6 +7352,7 @@ define run_playwright_test
@$(MAKE) --no-print-directory playwright-preflight
$(if $(strip $(2)),@mkdir -p $(2),)
@$(if $(strip $(3)),$(3),) TEST_BASE_URL='$(TEST_BASE_URL)' \
JWT_SECRET_KEY='$(JWT_SECRET_KEY)' PLATFORM_ADMIN_PASSWORD='$(PLATFORM_ADMIN_PASSWORD)' \
$(UV_BIN) run pytest -p playwright $(4) \
--browser chromium \
$(if $(filter fail,$(5)),|| { echo '❌ UI tests failed!'; exit 1; },|| true)
Expand Down
13 changes: 5 additions & 8 deletions tests/playwright/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,15 +381,12 @@ def admin_api(playwright: Playwright) -> Generator[APIRequestContext, None, None
if not token and not DISABLE_JWT_FALLBACK:
# Priority 3: Locally-signed JWT fallback
try:
token = _create_jwt_token(
{"sub": ADMIN_EMAIL},
user_data={
"email": ADMIN_EMAIL,
"full_name": "Test Admin",
"is_admin": True,
"auth_provider": "test",
},
token = make_test_jwt(
ADMIN_EMAIL,
is_admin=True,
teams=None, # Admin bypass: null teams with is_admin=true
auth_provider="test",
user_data={"email": ADMIN_EMAIL, "is_admin": True, "auth_provider": "test", "full_name": "Test Admin"},
)
except Exception:
pass # Use empty if generation fails
Expand Down
15 changes: 13 additions & 2 deletions tests/playwright/entities/test_gateways_extended.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,13 +567,24 @@ def test_edit_modal_auth_type_bearer_fields(self, gateways_page: GatewaysPage):

gateways_page.close_edit_modal()

def test_edit_modal_auth_type_oauth_fields(self, gateways_page: GatewaysPage):
def test_edit_modal_auth_type_oauth_fields(self, gateways_page: GatewaysPage, admin_api):
"""Test that selecting OAuth in edit modal shows OAuth fields."""
# Open a gateway whose auth type is NOT oauth: row 0 can be an
# OAuth-configured leftover from other suites, which legitimately
# renders OAuth fields on open and breaks the initial assertion.
resp = admin_api.get("/gateways")
assert resp.status == 200, f"Failed to list gateways: {resp.status}"
non_oauth = [g for g in resp.json() if g.get("authType") != "oauth"]
if not non_oauth:
pytest.skip("No non-OAuth gateway available")
gw_name = non_oauth[0]["name"]

gateways_page.navigate_to_gateways_tab()
gateways_page.wait_for_gateways_table_loaded()
_skip_if_no_gateways(gateways_page)

_open_edit_or_skip(gateways_page, 0)
gateways_page.search_gateways(gw_name)
gateways_page.open_edit_modal_by_name(gw_name)

# Wait for editGateway() to finish populating the form before asserting field
# states — the modal becomes visible before the async fetch resolves, so without
Expand Down
22 changes: 21 additions & 1 deletion tests/playwright/entities/test_servers_extended.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,12 +961,31 @@ def test_select_all_resources_survives_search(self, servers_page: ServersPage):

@pytest.mark.ui
@pytest.mark.e2e
def test_select_all_prompts_survives_search(self, servers_page: ServersPage):
def test_select_all_prompts_survives_search(self, servers_page: ServersPage, admin_api):
"""Regression test for #3257: Select All prompts + search + clear preserves selections.

Clicks Select All prompts, captures store size, searches, clears,
then asserts the in-memory store size is unchanged.
"""
# Guarantee at least one prompt is visible in the modal: a DB holding
# only team-scoped prompts from other suites leaves the prompt list
# empty and the wait below times out instead of reaching the skip.
prompt_name = f"sel-all-prompts-{uuid.uuid4().hex[:8]}"
resp = admin_api.post(
"/prompts/",
data={
"prompt": {
"name": prompt_name,
"description": "select-all regression prompt",
"template": "Hello {{name}}",
"arguments": [{"name": "name", "description": "Name", "required": True}],
},
"team_id": None,
},
)
assert resp.status in (200, 201), f"Failed to create prompt: {resp.status} {resp.text()}"
prompt_id = resp.json()["id"]

servers_page.navigate_to_servers_tab()
servers_page.wait_for_visible(servers_page.add_server_form)
server_name = self._create_server_with_tools(servers_page)
Expand Down Expand Up @@ -1036,3 +1055,4 @@ def test_select_all_prompts_survives_search(self, servers_page: ServersPage):
expect(servers_page.edit_server_modal).to_be_hidden(timeout=5000)
finally:
cleanup_server(servers_page.page, server_name)
admin_api.delete(f"/prompts/{prompt_id}")
14 changes: 14 additions & 0 deletions tests/playwright/pages/gateways_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,20 @@ def open_edit_modal(self, gateway_index: int = 0) -> None:
edit_btn = gateway_row.locator('button[role="menuitem"]:has-text("Edit")')
self._click_and_wait_for_gateway_fetch(edit_btn, "gateway-edit-modal")

def open_edit_modal_by_name(self, name: str) -> None:
"""Click Edit on the row for a named gateway (search-filter-safe).

Client-side search filters hide non-matching rows but leave them in the
table body, so index-based selection can land on a hidden row. This
locates the row by its visible gateway name instead.
"""
self.page.wait_for_selector('#gateways-table-body tr[id*="gateway-row"]', state="attached", timeout=15000)
self.page.wait_for_function("typeof window.Admin?.viewGateway === 'function'", timeout=10000)
gateway_row = self.gateways_table_body.locator("tr", has_text=name).first
self._open_action_dropdown(gateway_row)
edit_btn = gateway_row.locator('button[role="menuitem"]:has-text("Edit")')
self._click_and_wait_for_gateway_fetch(edit_btn, "gateway-edit-modal")

def close_edit_modal(self) -> None:
"""Close the edit modal via Cancel."""
self.edit_modal_cancel_btn.click()
Expand Down
2 changes: 1 addition & 1 deletion tests/playwright/test_rbac_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1194,7 +1194,7 @@ def test_developer_rpc_tools_call_not_denied(self, playwright: Playwright, admin
"tool": {
"name": tool_name,
"description": "RPC RBAC regression test tool (#3515)",
"url": "https://httpbin.org/get",
"url": "http://fast_time_server:9080/nonexistent", # local 404: fast, deterministic, non-retryable (httpbin.org is an external dependency that can degrade into retryable 503s, pushing the call past the client timeout)
"integration_type": "REST",
"input_schema": {},
"visibility": "team",
Expand Down
Loading