feat: import Chinese-localized Buzz source snapshot
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
import { expect, type Page, test } from "@playwright/test";
|
||||
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
|
||||
const CONNECT_ERROR = "relay unreachable: could not connect to relay";
|
||||
const PROXY_ERROR =
|
||||
"relay unreachable: relay returned an unexpected HTML page (network sign-in?)";
|
||||
const ACCESS_ERROR = "relay unreachable: 403 Forbidden";
|
||||
const RELAY_AUTH_ERROR = "Relay authentication rejected.";
|
||||
|
||||
type RelayConnectionState =
|
||||
| "connected"
|
||||
| "connecting"
|
||||
| "disconnected"
|
||||
| "idle"
|
||||
| "reconnecting"
|
||||
| "stalled";
|
||||
|
||||
async function setChannelsReadError(page: Page, error: string | null) {
|
||||
await page.evaluate((nextError) => {
|
||||
const testWindow = window as Window & {
|
||||
__BUZZ_E2E__?: { mock?: { channelsReadError?: string } };
|
||||
};
|
||||
|
||||
if (!testWindow.__BUZZ_E2E__?.mock) {
|
||||
throw new Error("Mock bridge config is not installed.");
|
||||
}
|
||||
|
||||
if (nextError === null) {
|
||||
delete testWindow.__BUZZ_E2E__.mock.channelsReadError;
|
||||
return;
|
||||
}
|
||||
|
||||
testWindow.__BUZZ_E2E__.mock.channelsReadError = nextError;
|
||||
}, error);
|
||||
}
|
||||
|
||||
async function setRelayConnectionState(
|
||||
page: Page,
|
||||
state: RelayConnectionState,
|
||||
) {
|
||||
if (state !== "connected") {
|
||||
// When driving to a degraded state, wait for the relay to reach "connected"
|
||||
// first. Without this guard, the mock relay's async auth handshake (started
|
||||
// on page load) can race the override and set state back to "connected"
|
||||
// after we've driven it to a degraded value.
|
||||
await page.waitForFunction(() => {
|
||||
const win = window as Window & {
|
||||
__BUZZ_E2E_GET_RELAY_CONNECTION_STATE__?: () => string;
|
||||
__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__?: unknown;
|
||||
};
|
||||
return (
|
||||
typeof win.__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__ === "function" &&
|
||||
typeof win.__BUZZ_E2E_GET_RELAY_CONNECTION_STATE__ === "function" &&
|
||||
win.__BUZZ_E2E_GET_RELAY_CONNECTION_STATE__() === "connected"
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// When driving to "connected", just wait for the seam to be installed —
|
||||
// no need to gate on the current state since we're overriding it directly.
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
typeof (
|
||||
window as Window & {
|
||||
__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__?: unknown;
|
||||
}
|
||||
).__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__ === "function",
|
||||
);
|
||||
}
|
||||
await page.evaluate((nextState) => {
|
||||
const testWindow = window as Window & {
|
||||
__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__?: (
|
||||
state: RelayConnectionState,
|
||||
) => void;
|
||||
};
|
||||
|
||||
const setConnectionState =
|
||||
testWindow.__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__;
|
||||
if (!setConnectionState) {
|
||||
throw new Error("Mock relay connection state helper is not installed.");
|
||||
}
|
||||
|
||||
setConnectionState(nextState);
|
||||
}, state);
|
||||
}
|
||||
|
||||
async function emitRelayConnectionState(
|
||||
page: Page,
|
||||
state: RelayConnectionState,
|
||||
) {
|
||||
await page.evaluate((nextState) => {
|
||||
const setConnectionState = (
|
||||
window as Window & {
|
||||
__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__?: (
|
||||
state: RelayConnectionState,
|
||||
) => void;
|
||||
}
|
||||
).__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__;
|
||||
if (!setConnectionState) {
|
||||
throw new Error("Mock relay connection state helper is not installed.");
|
||||
}
|
||||
setConnectionState(nextState);
|
||||
}, state);
|
||||
}
|
||||
|
||||
async function expectGenericReconnectCard(page: Page) {
|
||||
const card = page.getByTestId("sidebar-relay-unreachable");
|
||||
await expect(card).toBeVisible();
|
||||
await expect(card).toContainText("Can't reach the relay");
|
||||
await expect(card).toContainText("Click to connect");
|
||||
await expect(page.getByTestId("sidebar-reconnect")).toBeVisible();
|
||||
return card;
|
||||
}
|
||||
|
||||
test("sidebar generic relay failures use the reconnect card", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, { channelsReadError: CONNECT_ERROR });
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
// The card is gated on !isRelayConnectionConnected — a stale channelsReadError
|
||||
// alone (with state still "connected") no longer pins the card. Drive the relay
|
||||
// into a disconnected state so the card appears immediately (disconnected is
|
||||
// reported without debounce, unlike the 2-second delay for reconnecting/stalled).
|
||||
await setRelayConnectionState(page, "disconnected");
|
||||
|
||||
await expectGenericReconnectCard(page);
|
||||
});
|
||||
|
||||
test("relay outage notification stays dismissed through retries and re-arms after recovery", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, { channelsReadError: CONNECT_ERROR });
|
||||
await page.goto("/");
|
||||
await setRelayConnectionState(page, "disconnected");
|
||||
|
||||
const card = await expectGenericReconnectCard(page);
|
||||
await card
|
||||
.getByRole("button", { name: "Dismiss relay notification" })
|
||||
.click({ force: true });
|
||||
await expect(card).toBeHidden();
|
||||
|
||||
// Retry churn is still the same outage: no successful connection occurred.
|
||||
await emitRelayConnectionState(page, "connecting");
|
||||
await emitRelayConnectionState(page, "disconnected");
|
||||
await emitRelayConnectionState(page, "reconnecting");
|
||||
await page.waitForTimeout(2_100);
|
||||
await expect(card).toBeHidden();
|
||||
|
||||
// A successful connection ends the episode and re-arms the next outage.
|
||||
await setChannelsReadError(page, null);
|
||||
await emitRelayConnectionState(page, "connected");
|
||||
await setChannelsReadError(page, CONNECT_ERROR);
|
||||
await emitRelayConnectionState(page, "disconnected");
|
||||
await expectGenericReconnectCard(page);
|
||||
});
|
||||
|
||||
test("relay outage notification re-arms after same-URL lifecycle teardown", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, { channelsReadError: CONNECT_ERROR });
|
||||
await page.goto("/");
|
||||
await setRelayConnectionState(page, "disconnected");
|
||||
|
||||
const card = await expectGenericReconnectCard(page);
|
||||
await card
|
||||
.getByRole("button", { name: "Dismiss relay notification" })
|
||||
.click({ force: true });
|
||||
await expect(card).toBeHidden();
|
||||
|
||||
// Community switches and reconnectCommunity() tear down the singleton to
|
||||
// idle before applying the next lifecycle. The next lifecycle may reuse the
|
||||
// same relay URL, so URL identity alone must not preserve the old dismissal.
|
||||
await emitRelayConnectionState(page, "idle");
|
||||
await emitRelayConnectionState(page, "disconnected");
|
||||
await expectGenericReconnectCard(page);
|
||||
});
|
||||
|
||||
test("sidebar proxy sign-in failures use the reconnect card", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, { channelsReadError: PROXY_ERROR });
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
// Drive disconnected so the card appears immediately (connected is now authoritative).
|
||||
await setRelayConnectionState(page, "disconnected");
|
||||
|
||||
await expectGenericReconnectCard(page);
|
||||
});
|
||||
|
||||
test("sidebar access failures use the reconnect card", async ({ page }) => {
|
||||
await installMockBridge(page, { channelsReadError: ACCESS_ERROR });
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
// Drive disconnected so the card appears immediately (connected is now authoritative).
|
||||
await setRelayConnectionState(page, "disconnected");
|
||||
|
||||
await expectGenericReconnectCard(page);
|
||||
});
|
||||
|
||||
test("collapsed sidebar still shows the reconnect card", async ({ page }) => {
|
||||
await installMockBridge(page, { channelsReadError: CONNECT_ERROR });
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
// Drive degraded state so the card appears.
|
||||
await setRelayConnectionState(page, "disconnected");
|
||||
|
||||
await expectGenericReconnectCard(page);
|
||||
|
||||
await page
|
||||
.getByRole("button", { exact: true, name: "Toggle Sidebar" })
|
||||
.click();
|
||||
await expect(
|
||||
page.locator('[data-state="collapsed"][data-collapsible="offcanvas"]'),
|
||||
).toHaveCount(1);
|
||||
|
||||
// The card remains visible via the fixed overlay even with sidebar collapsed.
|
||||
const card = page.getByTestId("sidebar-relay-unreachable");
|
||||
await expect(card).toBeVisible();
|
||||
await expect(card).toContainText("Can't reach the relay");
|
||||
|
||||
await setChannelsReadError(page, null);
|
||||
await page.getByTestId("sidebar-reconnect").click();
|
||||
// Drive connected so the card shows success and auto-dismisses.
|
||||
await setRelayConnectionState(page, "connected");
|
||||
await expect(card).toContainText("Connected");
|
||||
await expect(card).toBeHidden({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("sidebar stalled relay state uses the reconnect card", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page);
|
||||
|
||||
await page.goto("/");
|
||||
await expect(page.getByTestId("channel-general")).toBeVisible();
|
||||
await setRelayConnectionState(page, "stalled");
|
||||
|
||||
await expectGenericReconnectCard(page);
|
||||
});
|
||||
|
||||
test("sidebar application auth disconnects stay on the error path", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, { channelsReadError: RELAY_AUTH_ERROR });
|
||||
|
||||
await page.goto("/");
|
||||
await setRelayConnectionState(page, "disconnected");
|
||||
|
||||
await expect(page.getByText(RELAY_AUTH_ERROR)).toBeVisible();
|
||||
await expect(page.getByTestId("sidebar-relay-unreachable")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("sidebar reconnect action shows connected before hiding", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, { channelsReadError: CONNECT_ERROR });
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
// Drive disconnected state so the card appears immediately.
|
||||
await setRelayConnectionState(page, "disconnected");
|
||||
|
||||
const card = await expectGenericReconnectCard(page);
|
||||
|
||||
await setChannelsReadError(page, null);
|
||||
await page.getByTestId("sidebar-reconnect").click();
|
||||
// Drive connected to simulate the relay recovering after the reconnect action.
|
||||
await setRelayConnectionState(page, "connected");
|
||||
|
||||
await expect(card).toContainText("Connected");
|
||||
await expect(card).not.toContainText("Click to connect");
|
||||
|
||||
await page.waitForTimeout(3_000);
|
||||
await expect(card).toContainText("Connected");
|
||||
await expect(card).toBeHidden({ timeout: 5_000 });
|
||||
});
|
||||
|
||||
test("sidebar reconnect action suppresses stale refresh errors after success", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, { channelsReadError: CONNECT_ERROR });
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
// Drive disconnected state so the card appears immediately.
|
||||
await setRelayConnectionState(page, "disconnected");
|
||||
|
||||
const card = await expectGenericReconnectCard(page);
|
||||
|
||||
await page.getByTestId("sidebar-reconnect").click();
|
||||
// Drive connected to simulate the relay recovering after the reconnect action.
|
||||
await setRelayConnectionState(page, "connected");
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await expect(card).toBeVisible();
|
||||
await expect(card).toContainText("Connected");
|
||||
await expect(card).not.toContainText("Can't reach the relay");
|
||||
|
||||
await page.waitForTimeout(6_500);
|
||||
await expect(card).toBeHidden();
|
||||
});
|
||||
|
||||
test("sidebar connected success clears when relay degrades again", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, { channelsReadError: CONNECT_ERROR });
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
// Drive disconnected state so the card appears immediately.
|
||||
await setRelayConnectionState(page, "disconnected");
|
||||
|
||||
const card = await expectGenericReconnectCard(page);
|
||||
|
||||
await setChannelsReadError(page, null);
|
||||
await page.getByTestId("sidebar-reconnect").click();
|
||||
// Drive connected to simulate the relay recovering after the reconnect action.
|
||||
await setRelayConnectionState(page, "connected");
|
||||
|
||||
await expect(card).toContainText("Connected");
|
||||
|
||||
await setRelayConnectionState(page, "stalled");
|
||||
|
||||
await expect(card).toContainText("Can't reach the relay", {
|
||||
timeout: 10_000,
|
||||
});
|
||||
await expect(card).toContainText("Click to connect");
|
||||
await expect(card).not.toContainText("Connected");
|
||||
});
|
||||
Reference in New Issue
Block a user