Files
buzz/desktop/tests/e2e/animated-avatar.spec.ts
klopez4212 81fba7bda4 Refresh desktop onboarding flow (#7528)
## Summary
- apply the refreshed card layout across desktop onboarding
- update identity backup, recovery, and key presentation flows
- clarify AI connection choices and default model settings
- simplify the backup ceremony from creation through optional
verification
- bring the harness and provider setup flow forward with validation,
preserved state, and consistent navigation
- fix action alignment and stray card scrolling in the refreshed layout

## Validation
- pre-push desktop lint, typecheck, frontend tests, and native tests
passed
- focused onboarding Playwright coverage passed
- manually reviewed the refreshed onboarding and backup flow in the
native app
- just ci passed all sections except one unchanged process-cleanup
timing test; its exact rerun passed

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Signed-off-by: Clay Delk <clay.delk@gmail.com>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Clay Delk <clay.delk@gmail.com>
2026-09-15 05:58:16 -04:00

345 lines
13 KiB
TypeScript

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();
const selectorBox = await page
.getByTestId("profile-avatar-animated-poster-selector")
.boundingBox();
const posterStripBox = await page
.getByTestId("profile-avatar-animated-poster-scrubber")
.boundingBox();
if (!selectorBox || !posterStripBox) {
throw new Error("Animated avatar poster selector bounds are missing.");
}
expect(selectorBox.x - posterStripBox.x).toBeGreaterThanOrEqual(8);
expect(selectorBox.y - posterStripBox.y).toBeGreaterThanOrEqual(8);
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,
});
await expect(
page.getByTestId("profile-avatar-preview-image"),
).toHaveAttribute("src", /^blob:/);
});
});