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,330 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
import { installFakeCamera } from "../helpers/fakeCamera";
|
||||
import { openSettings } from "../helpers/settings";
|
||||
|
||||
// The review editor (preview + framing + poster strip + backdrop panel) is
|
||||
// taller than the default 720px viewport — raise it so the whole editor remains visible.
|
||||
test.use({ viewport: { height: 1280, width: 1280 } });
|
||||
|
||||
const UPLOADED_PNG_DESCRIPTOR = {
|
||||
sha256: "ab".repeat(32),
|
||||
size: 4096,
|
||||
type: "image/png",
|
||||
uploaded: 1700000000,
|
||||
url: `https://relay.example.com/media/${"ab".repeat(32)}.png`,
|
||||
};
|
||||
|
||||
async function openAnimatedTab(
|
||||
page: import("@playwright/test").Page,
|
||||
options: { cameraDelayMs?: number; holdCamera?: boolean } = {},
|
||||
) {
|
||||
await installFakeCamera(page, options);
|
||||
await installMockBridge(page, {
|
||||
uploadDescriptors: [UPLOADED_PNG_DESCRIPTOR],
|
||||
});
|
||||
await page.goto("/");
|
||||
await openSettings(page, "profile");
|
||||
await page.getByTestId("profile-avatar-edit").click();
|
||||
await page.getByRole("tab", { name: "Animated" }).click();
|
||||
await expect(page.getByTestId("profile-avatar-animated")).toBeVisible();
|
||||
// Let the tab indicator + content height animations settle.
|
||||
await page.waitForTimeout(400);
|
||||
}
|
||||
|
||||
test.describe("animated avatar", () => {
|
||||
test("01 — animated tab idle state", async ({ page }) => {
|
||||
await openAnimatedTab(page);
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-camera-iphone"),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-camera-computer"),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-camera-iphone"),
|
||||
).toHaveAttribute("aria-pressed", "false");
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-camera-computer"),
|
||||
).toHaveAttribute("aria-pressed", "false");
|
||||
await expect(page.getByTestId("profile-avatar-animated-start")).toHaveCount(
|
||||
0,
|
||||
);
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-camera-select"),
|
||||
).toHaveCount(0);
|
||||
await expect(page.getByTestId("profile-avatar-done")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("02 — live camera preview", async ({ page }) => {
|
||||
await openAnimatedTab(page, { holdCamera: true });
|
||||
const animatedTabHeight = () =>
|
||||
page
|
||||
.getByTestId("profile-avatar-animated")
|
||||
.evaluate((element) =>
|
||||
Math.round(element.getBoundingClientRect().height),
|
||||
);
|
||||
const idleHeight = await animatedTabHeight();
|
||||
|
||||
await page.getByTestId("profile-avatar-animated-camera-iphone").click();
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
(
|
||||
window as Window & {
|
||||
__BUZZ_E2E_CAMERA_REQUEST_COUNT__?: number;
|
||||
}
|
||||
).__BUZZ_E2E_CAMERA_REQUEST_COUNT__ ?? 0,
|
||||
),
|
||||
)
|
||||
.toBeGreaterThan(0);
|
||||
const previewSlot = page.getByTestId(
|
||||
"profile-avatar-animated-preview-slot",
|
||||
);
|
||||
await expect(
|
||||
page.getByRole("status").filter({ hasText: "Starting camera" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
previewSlot.getByRole("status").filter({ hasText: "Starting camera" }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByTestId("profile-avatar-preview")).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-record"),
|
||||
).toHaveCount(0);
|
||||
const startingHeight = await animatedTabHeight();
|
||||
expect(Math.abs(startingHeight - idleHeight)).toBeLessThanOrEqual(1);
|
||||
|
||||
await page.evaluate(() => {
|
||||
(
|
||||
window as Window & {
|
||||
__BUZZ_E2E_RELEASE_CAMERA__?: () => void;
|
||||
}
|
||||
).__BUZZ_E2E_RELEASE_CAMERA__?.();
|
||||
});
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-record"),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
await expect(
|
||||
previewSlot.getByTestId("profile-avatar-animated-preview"),
|
||||
).toBeVisible();
|
||||
const liveHeight = await animatedTabHeight();
|
||||
expect(Math.abs(liveHeight - idleHeight)).toBeLessThanOrEqual(1);
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
(
|
||||
window as Window & {
|
||||
__BUZZ_E2E_CAMERA_CONSTRAINTS__?: MediaStreamConstraints[];
|
||||
}
|
||||
).__BUZZ_E2E_CAMERA_CONSTRAINTS__?.at(-1)?.video,
|
||||
),
|
||||
)
|
||||
.toEqual(
|
||||
expect.objectContaining({
|
||||
deviceId: { exact: "iphone-continuity" },
|
||||
facingMode: { ideal: "user" },
|
||||
}),
|
||||
);
|
||||
await expect(page.getByTestId("profile-avatar-done")).toHaveCount(0);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.getByTestId("profile-avatar-animated-record").click();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-sections"),
|
||||
).toBeVisible({ timeout: 60_000 });
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-size"),
|
||||
).toHaveAttribute("aria-valuenow", "116");
|
||||
});
|
||||
|
||||
test("03 — recording ring, pop-out review, custom color", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.setTimeout(120_000);
|
||||
await openAnimatedTab(page);
|
||||
await page.getByTestId("profile-avatar-animated-camera-computer").click();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-record"),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
await page.getByTestId("profile-avatar-animated-record").click();
|
||||
|
||||
// Mid-recording: the timer stroke sweeps around the capture circle.
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-timer-ring"),
|
||||
).toBeVisible();
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Review: the section icon row appears once processing finishes.
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-sections"),
|
||||
).toBeVisible({ timeout: 60_000 });
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-size"),
|
||||
).toHaveAttribute("aria-valuenow", "126");
|
||||
await expect(page.getByTestId("profile-avatar-done")).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-framing-readout"),
|
||||
).toHaveCount(0);
|
||||
|
||||
const previewSnapshot = () =>
|
||||
page
|
||||
.getByTestId("profile-avatar-animated-review-preview")
|
||||
.evaluate((canvas) => (canvas as HTMLCanvasElement).toDataURL());
|
||||
|
||||
// Color section: pick a deterministic backdrop color (the default is random).
|
||||
await page.getByTestId("profile-avatar-animated-section-color").click();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-backdrop-none"),
|
||||
).toHaveCount(0);
|
||||
await page
|
||||
.getByTestId("profile-avatar-animated-backdrop-grid")
|
||||
.locator("button")
|
||||
.nth(5)
|
||||
.click();
|
||||
|
||||
// Poster section: selecting a frame updates the (paused) preview.
|
||||
await page.getByTestId("profile-avatar-animated-section-poster").click();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-poster-strip"),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-poster-selector"),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-review-help"),
|
||||
).toHaveText("Pick the still shown before hover.");
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-poster-prev"),
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-poster-next"),
|
||||
).toHaveCount(0);
|
||||
const scrubber = page.getByTestId(
|
||||
"profile-avatar-animated-poster-scrubber",
|
||||
);
|
||||
const scrubberBox = await scrubber.boundingBox();
|
||||
if (!scrubberBox) {
|
||||
throw new Error("Animated avatar poster scrubber did not render bounds.");
|
||||
}
|
||||
await scrubber.click({
|
||||
position: {
|
||||
x: scrubberBox.width * 0.72,
|
||||
y: scrubberBox.height / 2,
|
||||
},
|
||||
});
|
||||
await expect(scrubber).not.toHaveAttribute("aria-valuenow", "0");
|
||||
|
||||
// The Circle tool stays implemented, but it is hidden in the review row for now.
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-section-shape"),
|
||||
).toHaveCount(0);
|
||||
|
||||
// You section: the DialKit-style local slider re-composes the preview.
|
||||
await page.getByTestId("profile-avatar-animated-section-person").click();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-review-help"),
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-outline-toggle"),
|
||||
).toHaveAttribute("aria-pressed", "true");
|
||||
await page.getByTestId("profile-avatar-animated-outline-toggle").click();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-outline-toggle"),
|
||||
).toHaveAttribute("aria-pressed", "false");
|
||||
await page.getByTestId("profile-avatar-animated-outline-toggle").click();
|
||||
const sizeSlider = page.getByTestId("profile-avatar-animated-size");
|
||||
const initialSizeValue = await sizeSlider.getAttribute("aria-valuenow");
|
||||
const sizeSliderBox = await sizeSlider.boundingBox();
|
||||
if (!sizeSliderBox) {
|
||||
throw new Error("Animated avatar size slider did not render bounds.");
|
||||
}
|
||||
await sizeSlider.click({
|
||||
position: {
|
||||
x: sizeSliderBox.width * 0.25,
|
||||
y: sizeSliderBox.height / 2,
|
||||
},
|
||||
});
|
||||
await expect(sizeSlider).not.toHaveAttribute(
|
||||
"aria-valuenow",
|
||||
initialSizeValue ?? "",
|
||||
);
|
||||
// Restore defaults so the standard framing remains covered.
|
||||
await page.getByTestId("profile-avatar-animated-reset-framing").click();
|
||||
await page.waitForTimeout(400);
|
||||
|
||||
// Custom backdrop color panel (shared HSV picker).
|
||||
await page.getByTestId("profile-avatar-animated-section-color").click();
|
||||
await page.getByTestId("profile-avatar-animated-backdrop-custom").click();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-custom-color-spectrum"),
|
||||
).toBeVisible();
|
||||
await expect(page.getByTestId("profile-avatar-done")).toHaveCount(0);
|
||||
const beforeCustomColor = await previewSnapshot();
|
||||
const customColorSpectrum = page.getByTestId(
|
||||
"profile-avatar-animated-custom-color-spectrum",
|
||||
);
|
||||
const customColorSpectrumBox = await customColorSpectrum.boundingBox();
|
||||
if (!customColorSpectrumBox) {
|
||||
throw new Error(
|
||||
"Animated avatar custom color picker did not render bounds.",
|
||||
);
|
||||
}
|
||||
await customColorSpectrum.click({
|
||||
position: {
|
||||
x: customColorSpectrumBox.width * 0.88,
|
||||
y: customColorSpectrumBox.height * 0.82,
|
||||
},
|
||||
});
|
||||
await page.waitForTimeout(400);
|
||||
const customColorPreview = await previewSnapshot();
|
||||
expect(customColorPreview).not.toEqual(beforeCustomColor);
|
||||
await page.getByTestId("profile-avatar-animated-custom-color-done").click();
|
||||
await expect(
|
||||
page.getByTestId("profile-avatar-animated-backdrop-custom"),
|
||||
).toHaveAttribute("aria-pressed", "true");
|
||||
expect(await previewSnapshot()).toEqual(customColorPreview);
|
||||
|
||||
// Done applies the recording (APNG encode + mocked two-file upload) and
|
||||
// then saves the profile in one step.
|
||||
const doneButton = page.getByTestId("profile-avatar-done");
|
||||
await doneButton.click();
|
||||
await expect(doneButton).toContainText("Saving", { timeout: 2_000 });
|
||||
await expect(page.getByRole("tab", { name: "Image" })).toBeDisabled();
|
||||
await expect(page.getByRole("tab", { name: "Emoji" })).toBeDisabled();
|
||||
await expect(page.getByRole("tab", { name: "Animated" })).toBeDisabled();
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
(
|
||||
window as Window & { __BUZZ_E2E_COMMANDS__?: string[] }
|
||||
).__BUZZ_E2E_COMMANDS__?.filter(
|
||||
(command) => command === "upload_media_bytes",
|
||||
).length ?? 0,
|
||||
),
|
||||
{ timeout: 30_000 },
|
||||
)
|
||||
.toBeGreaterThanOrEqual(2);
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
(window as Window & { __BUZZ_E2E_COMMANDS__?: string[] })
|
||||
.__BUZZ_E2E_COMMANDS__ ?? [],
|
||||
),
|
||||
{ timeout: 30_000 },
|
||||
)
|
||||
.toEqual(expect.arrayContaining(["update_profile"]));
|
||||
await expect(page.getByTestId("profile-avatar-animated-error")).toHaveCount(
|
||||
0,
|
||||
);
|
||||
await expect(page.getByTestId("profile-avatar-preview")).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user