텍스트 응답 음성 재생 연결
This commit is contained in:
parent
64e06a1185
commit
d80e33da5e
9 changed files with 524 additions and 16 deletions
|
|
@ -139,6 +139,25 @@ async function startFakeEngine(): Promise<TestServer> {
|
|||
);
|
||||
return;
|
||||
}
|
||||
if (req.method === "POST" && req.url === "/v1/stream") {
|
||||
res.writeHead(200, {
|
||||
"content-type": "text/event-stream",
|
||||
"cache-control": "no-cache",
|
||||
});
|
||||
res.write(`event: token\ndata: ${JSON.stringify({ text: "괜찮아요. " })}\n\n`);
|
||||
res.write(`event: token\ndata: ${JSON.stringify({ text: "천천히 말해볼게요." })}\n\n`);
|
||||
res.end(
|
||||
`event: done\ndata: ${JSON.stringify({
|
||||
provider: "e2e",
|
||||
model: "fake-client",
|
||||
tokens_in: 1,
|
||||
tokens_out: 1,
|
||||
cost_usd: 0,
|
||||
turns: 1,
|
||||
})}\n\n`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
res.writeHead(404, { "content-type": "application/json" });
|
||||
res.end(JSON.stringify({ error: "not found" }));
|
||||
});
|
||||
|
|
@ -836,7 +855,7 @@ test.describe("voice cascade success path", () => {
|
|||
test("drives one voice turn through the Session mic UI with synthetic browser audio @single-run", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
test.setTimeout(90_000);
|
||||
test.setTimeout(150_000);
|
||||
|
||||
const diagnostics: string[] = [];
|
||||
page.on("pageerror", (error) => diagnostics.push(`pageerror: ${error.message}`));
|
||||
|
|
@ -848,7 +867,10 @@ test.describe("voice cascade success path", () => {
|
|||
});
|
||||
page.on("response", (response) => {
|
||||
const url = response.url();
|
||||
if (response.status() >= 400 && (url.includes("/sessions") || url.includes("/voice/ws"))) {
|
||||
if (
|
||||
response.status() >= 400 &&
|
||||
(url.includes("/sessions") || url.includes("/voice/ws") || url.includes("/voice/speech"))
|
||||
) {
|
||||
diagnostics.push(`response: ${response.status()} ${url}`);
|
||||
}
|
||||
});
|
||||
|
|
@ -964,6 +986,39 @@ test.describe("voice cascade success path", () => {
|
|||
].join("\n\n"),
|
||||
).toBeVisible({ timeout: 20_000 });
|
||||
|
||||
const textInput = page.getByLabel("학습자 발화 입력");
|
||||
const sendButton = page.getByRole("button", { name: "보내기", exact: true });
|
||||
await textInput.fill("텍스트로 말해도 내담자 음성을 들려주세요.");
|
||||
await sendButton.click();
|
||||
await expect
|
||||
.poll(
|
||||
() => openai.requests().filter((request) => request === "POST /v1/audio/speech").length,
|
||||
{ timeout: 30_000 },
|
||||
)
|
||||
.toBeGreaterThan(0);
|
||||
await expect
|
||||
.poll(async () => (await readVoiceUiProbe(page)).audioBufferStarts, { timeout: 30_000 })
|
||||
.toBeGreaterThan(0);
|
||||
await expect(page.locator(".sx-utt").filter({ hasText: "괜찮아요. 천천히 말해볼게요." })).toBeVisible();
|
||||
|
||||
const freshVoiceSession = await page.evaluate(async ({ apiBase, personaCode }) => {
|
||||
const response = await fetch(`${apiBase}/sessions`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ persona_code: personaCode, theory_mode: "humanistic" }),
|
||||
});
|
||||
return {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
body: await response.json() as { session_id?: string },
|
||||
};
|
||||
}, { apiBase: api.baseURL, personaCode: SEEDED_VOICE_PERSONA_CODE });
|
||||
expect(freshVoiceSession, api.logs()).toMatchObject({ ok: true });
|
||||
expect(typeof freshVoiceSession.body.session_id).toBe("string");
|
||||
await page.goto(`${web.baseURL}/learn/session/${freshVoiceSession.body.session_id}`);
|
||||
await expect(page.locator(".sx-page.sx-page--active")).toBeVisible({ timeout: 20_000 });
|
||||
|
||||
const mic = page.locator(".sx-mic");
|
||||
await expect(mic).toBeEnabled();
|
||||
await mic.click();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue