test(e2e): repair the four shards the first green Build finally exercised

test-e2e has `needs: [build]`, and the release PR's Build died on every round
until now — so the 9-shard matrix produced ZERO signal for this whole cycle
while ~200 PRs merged. The first successful Build surfaced four independent
breakages, each traced to the commit that caused it:

- providers-management (#7361): the single-connection delete moved from
  window.confirm() to a ConfirmModal, so page.once("dialog") never fired and
  the DELETE was never sent (deleteCalls stayed 0). Click the modal instead.
- providers-bailian-coding-plan (#7882): the free-text Base URL field was
  deliberately replaced by a region step whose choice resolves the endpoint
  (global-sg -> coding-intl.dashscope, china-beijing -> coding.dashscope).
  Both cases rewritten against the region step; the invalid-URL case is
  unreachable from this modal now, so it covers the CN choice instead.
- group-b-activity-feed: the stack-trace guard ran against page.content(),
  which embeds the serialized i18n payload — zenmux's "endpoint at
  /api/v1/chat/completions" is prose, not a leak. Assert on rendered
  innerText and require the :line:col every real stack frame carries.
- navigation (#8292): APP_ROUTE_PATTERN accepted only /login and /dashboard,
  but the new prefetch spec is the sole caller passing /home, so waitForURL
  never resolved and the retry loop burned the full 180s timeout.

E2E is green on main (9/9 on 07-22 and 07-23), so all four are cycle
regressions, not pre-existing debt. Tests only — no production code touched.
This commit is contained in:
diegosouzapw
2026-07-29 08:41:41 -03:00
parent f9fbb54fd9
commit 0ffc08b7e5
4 changed files with 61 additions and 61 deletions

View File

@@ -89,8 +89,13 @@ test.describe("Group B — Activity Feed", () => {
await gotoDashboardRoute(page, "/dashboard/activity");
const pageContent = await page.content();
// Stack traces should never appear in the UI (Hard Rule #12)
expect(pageContent).not.toMatch(/\s+at\s+\//);
// Assert on what the user actually SEES, not on page.content(): the full HTML
// embeds the serialized i18n payload, where ordinary provider prose trips a
// naive stack-trace match ("...endpoint at /api/v1/chat/completions" — zenmux).
// innerText carries only rendered text, which is exactly what Hard Rule #12 is
// about. The pattern also requires the :line:col suffix every real stack frame
// has, so English sentences containing " at /" can never masquerade as a leak.
const visibleText = await page.locator("body").innerText();
expect(visibleText).not.toMatch(/\s+at\s+\/\S*:\d+:\d+/);
});
});

View File

@@ -6,7 +6,11 @@ type GotoDashboardRouteOptions = {
};
const DEFAULT_TIMEOUT_MS = 300_000;
const APP_ROUTE_PATTERN = /\/(login|dashboard)(\/[^?#]*)?([?#].*)?$/;
// `home` belongs here too: it is an app route under the (dashboard) group but does
// NOT sit under the /dashboard prefix. Without it, waitForURL() never resolves for
// gotoDashboardRoute(page, "/home") — the retry loop just burns the whole test
// timeout with no assertion error, which is how #8292's prefetch spec hung.
const APP_ROUTE_PATTERN = /\/(login|dashboard|home)(\/[^?#]*)?([?#].*)?$/;
const E2E_PASSWORD =
process.env.OMNIROUTE_E2E_PASSWORD || process.env.INITIAL_PASSWORD || "omniroute-e2e-password";

View File

@@ -1,12 +1,17 @@
import { expect, test } from "@playwright/test";
import { gotoDashboardRoute } from "./helpers/dashboardAuth";
const DEFAULT_BAILIAN_URL = "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1";
// #7882 replaced this provider's free-text Base URL field with a region step:
// the endpoint is now derived from the choice ("global-sg" ->
// coding-intl.dashscope.aliyuncs.com, "china-beijing" -> coding.dashscope.aliyuncs.com,
// see src/shared/constants/alibabaProviderRegions.ts), so the modal persists
// providerSpecificData.region instead of a baseUrl. A per-connection base-URL
// override still exists, but it moved to Advanced in the edit-connection modal.
test.describe("Bailian Coding Plan Provider", () => {
test.describe.configure({ mode: "serial" });
test("default URL visible and editable in Add API Key modal", async ({ page }) => {
test("region step persists the international (Singapore) choice", async ({ page }) => {
const capturedPayloads: { createProvider?: Record<string, unknown> } = {};
await page.route("**/api/providers", async (route) => {
@@ -80,14 +85,10 @@ test.describe("Bailian Coding Plan Provider", () => {
const dialog = page.getByRole("dialog").first();
await expect(dialog).toBeVisible({ timeout: 10000 });
const baseUrlInput = dialog
.getByLabel(/base.*url/i)
.or(dialog.locator("input").filter({ has: page.locator("..").getByText(/base.*url/i) }));
await expect(baseUrlInput).toBeVisible({ timeout: 15000 });
const inputValue = await baseUrlInput.inputValue();
expect(inputValue).toBe(DEFAULT_BAILIAN_URL);
const regionStep = dialog.getByTestId("alibaba-region-step");
await expect(regionStep).toBeVisible({ timeout: 15000 });
await expect(regionStep.locator("[data-region]")).toHaveCount(2);
await regionStep.locator('[data-region="global-sg"]').click();
const nameInput = dialog.getByLabel(/name/i).or(dialog.locator("input").first());
await nameInput.fill("Test Bailian Connection");
@@ -97,9 +98,6 @@ test.describe("Bailian Coding Plan Provider", () => {
.or(dialog.locator('input[type="password"]').first());
await apiKeyInput.fill("test-api-key-12345");
const customUrl = "https://custom.example.com/anthropic/v1";
await baseUrlInput.fill(customUrl);
const saveButton = dialog
.getByRole("button", {
name: /save|add|create|connect/i,
@@ -115,11 +113,16 @@ test.describe("Bailian Coding Plan Provider", () => {
expect(capturedPayloads.createProvider).toBeDefined();
const payload = capturedPayloads.createProvider;
expect(payload?.providerSpecificData).toBeDefined();
expect((payload?.providerSpecificData as Record<string, unknown>)?.baseUrl).toBe(customUrl);
expect((payload?.providerSpecificData as Record<string, unknown>)?.region).toBe("global-sg");
});
test("invalid URL blocks save with validation error", async ({ page }) => {
let createAttempted = false;
// The old "invalid URL blocks save" case tested client-side validation of the
// free-text Base URL field, which #7882 removed for this provider — an invalid
// URL is no longer reachable from this modal. Replaced with the other half of
// the region contract: the China-mainland choice must persist as typed, since
// that is what selects the coding.dashscope.aliyuncs.com endpoint.
test("region step persists the China-mainland (Beijing) choice", async ({ page }) => {
const capturedPayloads: { createProvider?: Record<string, unknown> } = {};
await page.route("**/api/providers", async (route) => {
const method = route.request().method();
@@ -133,18 +136,19 @@ test.describe("Bailian Coding Plan Provider", () => {
}
if (method === "POST") {
createAttempted = true;
const payload = route.request().postDataJSON();
capturedPayloads.createProvider = payload;
await route.fulfill({
status: 400,
status: 200,
contentType: "application/json",
body: JSON.stringify({
message: "Invalid request",
details: [
{
field: "providerSpecificData.baseUrl",
message: "providerSpecificData.baseUrl must be a valid URL",
},
],
connection: {
id: "conn-bailian-cn",
provider: "bailian-coding-plan",
name: payload.name || "Test Connection",
testStatus: "active",
providerSpecificData: payload.providerSpecificData,
},
}),
});
return;
@@ -191,50 +195,33 @@ test.describe("Bailian Coding Plan Provider", () => {
const dialog = page.getByRole("dialog").first();
await expect(dialog).toBeVisible({ timeout: 10000 });
const baseUrlInput = dialog
.getByLabel(/base.*url/i)
.or(dialog.locator("input").filter({ has: page.locator("..").getByText(/base.*url/i) }));
await expect(baseUrlInput).toBeVisible({ timeout: 15000 });
const regionStep = dialog.getByTestId("alibaba-region-step");
await expect(regionStep).toBeVisible({ timeout: 15000 });
await regionStep.locator('[data-region="china-beijing"]').click();
const nameInput = dialog.getByLabel(/name/i).or(dialog.locator("input").first());
await nameInput.fill("Test Invalid URL Connection");
await nameInput.fill("Test Bailian CN Connection");
const apiKeyInput = dialog
.getByLabel(/api.*key/i)
.or(dialog.locator('input[type="password"]').first());
await apiKeyInput.fill("test-api-key-12345");
await baseUrlInput.fill("not-a-url");
const saveButton = dialog
.getByRole("button", {
name: /save|add|create|connect/i,
})
.last();
await expect(saveButton).toBeEnabled({ timeout: 15000 });
await saveButton.click();
// Wait for React to process the validation and re-render
await page.waitForTimeout(1000);
await expect(dialog)
.toBeHidden({ timeout: 10000 })
.catch(() => undefined);
// Check for the validation error scoped to the dialog to avoid strict-mode
// violations from broad selectors matching unrelated page elements.
const errorTextLocator = dialog
.locator("text=/invalid.*url|url.*invalid|must be a valid url|must use http/i")
.first();
const errorClassLocator = dialog.locator(".text-red-500").first();
let errorVisible =
(await errorTextLocator.isVisible().catch(() => false)) ||
(await errorClassLocator.isVisible().catch(() => false));
if (!errorVisible) {
// Fallback: if the dialog stays open after clicking save, it means the
// client-side validation prevented submission (which is the desired behavior).
await page.waitForTimeout(2000);
errorVisible = await dialog.isVisible().catch(() => false);
}
expect(errorVisible).toBe(true);
expect(createAttempted).toBe(false);
expect(capturedPayloads.createProvider).toBeDefined();
const payload = capturedPayloads.createProvider;
expect(payload?.providerSpecificData).toBeDefined();
expect((payload?.providerSpecificData as Record<string, unknown>)?.region).toBe("china-beijing");
});
});

View File

@@ -312,10 +312,14 @@ test.describe("Providers management", () => {
).__providersTestState.forceInvalidValidation = false;
});
page.once("dialog", async (dialog) => {
await dialog.accept();
});
// #7361 replaced the native window.confirm() with a ConfirmModal, so the old
// page.once("dialog") handler never fires and the delete request was never sent.
await page.getByTitle(/^delete$/i).click();
const confirmDialog = page
.getByRole("dialog")
.filter({ hasText: /delete this connection/i });
await expect(confirmDialog).toBeVisible({ timeout: 10000 });
await confirmDialog.getByRole("button", { name: /^delete$/i }).click();
await expect.poll(async () => (await readProviderMockState(page)).deleteCalls).toBe(1);
await expect(page.getByText("Primary OpenAI Edited")).toHaveCount(0);