An empty draft shows the mic where the send arrow sits; it opens the voice conversation as the separate Voice button did. As soon as the draft holds text, a chip or an attachment, the slot turns back into Send. This frees the width the extra Voice pill took on small screens. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XcoERsCTbvjqDzE8qC82Wo
105 lines
5.1 KiB
TypeScript
105 lines
5.1 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { captureScreenshot, completeOnboarding, openUserSettings, rpc, signup } from "./helpers";
|
|
|
|
test("voice settings connect a key, speak a reply, and open a call", async ({ page }, testInfo) => {
|
|
const stamp = Date.now();
|
|
const userName = `Voice ${stamp}`;
|
|
await signup(page, `voice-${stamp}@rakazo.test`, "password12", userName);
|
|
await completeOnboarding(page);
|
|
|
|
await expect(page.getByRole("button", { name: "Call", exact: true })).toHaveCount(0);
|
|
await expect(
|
|
page.getByTestId("composer-bar").getByRole("button", { name: "Voice", exact: true }),
|
|
).toHaveCount(1);
|
|
await captureScreenshot(page, testInfo, "voice-composer");
|
|
await page
|
|
.getByTestId("composer-bar")
|
|
.getByRole("button", { name: "Voice", exact: true })
|
|
.click();
|
|
await expect(page.getByTestId("voice-settings")).toBeVisible();
|
|
await page.getByRole("button", { name: "Other voice providers" }).click();
|
|
await expect(page.getByLabel("API key", { exact: true })).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "Dictate", exact: true })).toHaveCount(0);
|
|
await captureScreenshot(page, testInfo, "voice-settings");
|
|
await page.getByRole("button", { name: "Close voice settings" }).click();
|
|
await expect(page.getByTestId("voice-settings")).toHaveCount(0);
|
|
|
|
const preparedOff = await rpc<{ ready: boolean }>(page, "voice/prepare", {
|
|
text: "Hello there.",
|
|
});
|
|
expect(preparedOff.ready).toBe(false);
|
|
|
|
await openUserSettings(page, "voice");
|
|
await expect(page.getByTestId("voice-settings")).toBeVisible();
|
|
await page.getByRole("button", { name: "Other voice providers" }).click();
|
|
await page.getByLabel("Provider", { exact: true }).selectOption("scripted");
|
|
const apiKeyInput = page.getByPlaceholder(/Paste your API key/);
|
|
await expect(apiKeyInput).toHaveAttribute("autocomplete", "new-password");
|
|
await apiKeyInput.fill("fake-scripted-voice-key");
|
|
await page.getByRole("button", { name: "Connect", exact: true }).click();
|
|
await expect(page.getByText("Connected Scripted.")).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "Replace key" })).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "Disconnect", exact: true })).toBeVisible();
|
|
await captureScreenshot(page, testInfo, "voice-settings-connected");
|
|
|
|
const credentials = await rpc<Array<{ hasKey: boolean; provider: string }>>(
|
|
page,
|
|
"voice/credentials",
|
|
{},
|
|
);
|
|
expect(credentials).toEqual([expect.objectContaining({ hasKey: true, provider: "scripted" })]);
|
|
expect(JSON.stringify(credentials)).not.toContain("fake-scripted-voice-key");
|
|
|
|
await page.getByRole("button", { name: "Close voice settings" }).click();
|
|
|
|
const composer = page.getByPlaceholder(/Message/);
|
|
await composer.fill("say hello");
|
|
await expect(
|
|
page.getByTestId("composer-bar").getByRole("button", { name: "Voice", exact: true }),
|
|
).toHaveCount(0);
|
|
await captureScreenshot(page, testInfo, "voice-composer-typing");
|
|
await page.keyboard.press("Enter");
|
|
// A reply can render more than one text bubble; speak the latest one.
|
|
const speakReply = page.getByRole("button", { name: "Speak this reply" }).last();
|
|
await expect(speakReply).toBeVisible({
|
|
timeout: 30_000,
|
|
});
|
|
|
|
const replySpoken = page.waitForResponse(
|
|
(response) => response.url().includes("/api/voice/speak") && response.ok(),
|
|
);
|
|
await speakReply.click();
|
|
await replySpoken;
|
|
|
|
await openUserSettings(page, "voice");
|
|
await page.getByRole("button", { name: "Other voice providers" }).click();
|
|
await page.getByLabel("Provider", { exact: true }).selectOption("scripted");
|
|
await expect(page.getByRole("button", { name: "Replace key" })).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "Disconnect", exact: true })).toBeVisible();
|
|
await page.getByRole("button", { name: "Close voice settings" }).click();
|
|
|
|
await page
|
|
.getByTestId("composer-bar")
|
|
.getByRole("button", { name: "Voice", exact: true })
|
|
.click();
|
|
await expect(page.getByTestId("call-view")).toBeVisible();
|
|
await expect(page.getByTestId("voice-panel")).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "Interrupt" })).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "Hang up" })).toBeVisible();
|
|
await captureScreenshot(page, testInfo, "voice-stage");
|
|
await page.getByRole("button", { name: "Hang up" }).click();
|
|
await expect(page.getByTestId("call-view")).toHaveCount(0);
|
|
|
|
await openUserSettings(page, "voice");
|
|
const settings = page.getByTestId("voice-settings");
|
|
await settings.getByRole("button", { name: "Other voice providers" }).click();
|
|
await settings.getByPlaceholder(/Paste a replacement key/).fill("leftover-voice-key");
|
|
await settings.getByRole("button", { name: "Disconnect", exact: true }).click();
|
|
await expect(settings.getByRole("button", { name: "Disconnect", exact: true })).toHaveCount(0);
|
|
await expect(settings.getByRole("button", { name: "Connect", exact: true })).toBeVisible();
|
|
await expect(settings.getByLabel("API key", { exact: true })).toHaveValue("");
|
|
await expect(rpc<Array<{ provider: string }>>(page, "voice/credentials", {})).resolves.toEqual(
|
|
[],
|
|
);
|
|
});
|