Skip to content

Commit 93747f6

Browse files
committed
test(qa): add discord voice autojoin smoke
1 parent 5a67b57 commit 93747f6

5 files changed

Lines changed: 363 additions & 2 deletions

File tree

docs/concepts/qa-e2e-automation.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,14 +316,24 @@ Required env when `--credential-source env`:
316316
Optional:
317317

318318
- `OPENCLAW_QA_DISCORD_CAPTURE_CONTENT=1` keeps message bodies in observed-message artifacts.
319+
- `OPENCLAW_QA_DISCORD_VOICE_CHANNEL_ID` selects the voice/stage channel for `discord-voice-autojoin`; without it, the scenario picks the first visible voice/stage channel for the SUT bot.
319320

320321
Scenarios (`extensions/qa-lab/src/live-transports/discord/discord-live.runtime.ts:36`):
321322

322323
- `discord-canary`
323324
- `discord-mention-gating`
324325
- `discord-native-help-command-registration`
326+
- `discord-voice-autojoin` - opt-in voice scenario. Runs by itself, enables `channels.discord.voice.autoJoin`, and verifies the SUT bot's current Discord voice state is the target voice/stage channel. Convex Discord credentials may include optional `voiceChannelId`; otherwise the runner discovers the first visible voice/stage channel in the guild.
325327
- `discord-status-reactions-tool-only` - opt-in Mantis scenario. Runs by itself because it switches the SUT to always-on, tool-only guild replies with `messages.statusReactions.enabled=true`, then captures a REST reaction timeline plus HTML/PNG visual artifacts. Mantis before/after reports also preserve scenario-provided MP4 artifacts as `baseline.mp4` and `candidate.mp4`.
326328

329+
Run the Discord voice auto-join scenario explicitly:
330+
331+
```bash
332+
pnpm openclaw qa discord \
333+
--scenario discord-voice-autojoin \
334+
--provider-mode mock-openai
335+
```
336+
327337
Run the Mantis status-reaction scenario explicitly:
328338

329339
```bash

extensions/qa-lab/src/live-transports/discord/discord-live.runtime.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,26 @@ describe("discord live qa runtime", () => {
3030
});
3131
});
3232

33+
it("resolves optional Discord QA voice channel env var", () => {
34+
expect(
35+
__testing.resolveDiscordQaRuntimeEnv({
36+
OPENCLAW_QA_DISCORD_GUILD_ID: "123456789012345678",
37+
OPENCLAW_QA_DISCORD_CHANNEL_ID: "223456789012345678",
38+
OPENCLAW_QA_DISCORD_VOICE_CHANNEL_ID: "523456789012345678",
39+
OPENCLAW_QA_DISCORD_DRIVER_BOT_TOKEN: "driver",
40+
OPENCLAW_QA_DISCORD_SUT_BOT_TOKEN: "sut",
41+
OPENCLAW_QA_DISCORD_SUT_APPLICATION_ID: "323456789012345678",
42+
}),
43+
).toEqual({
44+
guildId: "123456789012345678",
45+
channelId: "223456789012345678",
46+
voiceChannelId: "523456789012345678",
47+
driverBotToken: "driver",
48+
sutBotToken: "sut",
49+
sutApplicationId: "323456789012345678",
50+
});
51+
});
52+
3353
it("fails when a required Discord QA env var is missing", () => {
3454
expect(() =>
3555
__testing.resolveDiscordQaRuntimeEnv({
@@ -58,13 +78,15 @@ describe("discord live qa runtime", () => {
5878
__testing.parseDiscordQaCredentialPayload({
5979
guildId: "123456789012345678",
6080
channelId: "223456789012345678",
81+
voiceChannelId: "523456789012345678",
6182
driverBotToken: "driver",
6283
sutBotToken: "sut",
6384
sutApplicationId: "323456789012345678",
6485
}),
6586
).toEqual({
6687
guildId: "123456789012345678",
6788
channelId: "223456789012345678",
89+
voiceChannelId: "523456789012345678",
6890
driverBotToken: "driver",
6991
sutBotToken: "sut",
7092
sutApplicationId: "323456789012345678",
@@ -141,6 +163,35 @@ describe("discord live qa runtime", () => {
141163
});
142164
});
143165

166+
it("injects Discord voice auto-join config for the voice smoke", () => {
167+
const next = __testing.buildDiscordQaConfig(
168+
{},
169+
{
170+
guildId: "123456789012345678",
171+
channelId: "223456789012345678",
172+
driverBotId: "423456789012345678",
173+
sutAccountId: "sut",
174+
sutBotToken: "sut-token",
175+
},
176+
{
177+
voiceAutoJoin: {
178+
guildId: "123456789012345678",
179+
channelId: "523456789012345678",
180+
},
181+
},
182+
);
183+
184+
expect(next.channels?.discord?.voice).toEqual({
185+
enabled: true,
186+
autoJoin: [
187+
{
188+
guildId: "123456789012345678",
189+
channelId: "523456789012345678",
190+
},
191+
],
192+
});
193+
});
194+
144195
it("injects tool-only Discord status reaction config for the Mantis scenario", () => {
145196
const next = __testing.buildDiscordQaConfig(
146197
{},
@@ -250,6 +301,9 @@ describe("discord live qa runtime", () => {
250301
expect(
251302
__testing.findScenario(["discord-status-reactions-tool-only"]).map((scenario) => scenario.id),
252303
).toEqual(["discord-status-reactions-tool-only"]);
304+
expect(
305+
__testing.findScenario(["discord-voice-autojoin"]).map((scenario) => scenario.id),
306+
).toEqual(["discord-voice-autojoin"]);
253307
expect(
254308
__testing
255309
.findScenario(["discord-thread-reply-filepath-attachment"])
@@ -464,6 +518,60 @@ describe("discord live qa runtime", () => {
464518
]);
465519
});
466520

521+
it("discovers the first visible Discord voice channel for the voice smoke", async () => {
522+
vi.stubGlobal(
523+
"fetch",
524+
vi.fn(
525+
async () =>
526+
new Response(
527+
JSON.stringify([
528+
{ id: "123456789012345678", name: "general", position: 0, type: 0 },
529+
{ id: "523456789012345678", name: "qa-voice", position: 1, type: 2 },
530+
{ id: "623456789012345678", name: "stage", position: 2, type: 13 },
531+
]),
532+
{
533+
status: 200,
534+
headers: {
535+
"content-type": "application/json",
536+
},
537+
},
538+
),
539+
),
540+
);
541+
542+
await expect(
543+
__testing.resolveDiscordQaVoiceChannel({
544+
token: "token",
545+
guildId: "123456789012345678",
546+
}),
547+
).resolves.toMatchObject({
548+
id: "523456789012345678",
549+
name: "qa-voice",
550+
});
551+
});
552+
553+
it("normalizes missing current Discord voice state to null", async () => {
554+
vi.stubGlobal(
555+
"fetch",
556+
vi.fn(
557+
async () =>
558+
new Response(JSON.stringify({ message: "Unknown Voice State" }), {
559+
status: 404,
560+
headers: {
561+
"content-type": "application/json",
562+
},
563+
}),
564+
),
565+
);
566+
567+
await expect(
568+
__testing.getCurrentDiscordVoiceState({
569+
token: "token",
570+
guildId: "123456789012345678",
571+
}),
572+
).resolves.toBeNull();
573+
});
574+
467575
it("waits for required Discord application commands to be registered", async () => {
468576
vi.useFakeTimers();
469577
try {

0 commit comments

Comments
 (0)