test(app): block real llm calls in e2e prompts (#20579)

pull/20665/head
Kit Langton 2026-04-01 22:22:43 -04:00 committed by GitHub
parent df1c6c9e8d
commit a09b086729
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 67 additions and 10 deletions

View File

@ -1,5 +1,5 @@
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
import { expect, type Locator, type Page } from "@playwright/test"
import { expect, type Locator, type Page, type Route } from "@playwright/test"
import fs from "node:fs/promises"
import os from "node:os"
import path from "node:path"
@ -43,6 +43,27 @@ export async function defocus(page: Page) {
.catch(() => undefined)
}
export async function withNoReplyPrompt<T>(page: Page, fn: () => Promise<T>) {
const url = "**/session/*/prompt_async"
const route = async (input: Route) => {
const body = input.request().postDataJSON()
await input.continue({
postData: JSON.stringify({ ...body, noReply: true }),
headers: {
...input.request().headers(),
"content-type": "application/json",
},
})
}
await page.route(url, route)
try {
return await fn()
} finally {
await page.unroute(url, route)
}
}
async function terminalID(term: Locator) {
const id = await term.getAttribute(terminalAttr)
if (id) return id

View File

@ -10,6 +10,7 @@ import {
waitSession,
waitSessionSaved,
waitSlug,
withNoReplyPrompt,
} from "../actions"
import { projectSwitchSelector, promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
import { dirSlug, resolveDirectory } from "../utils"
@ -81,8 +82,10 @@ test("switching back to a project opens the latest workspace session", async ({
// Create a session by sending a prompt
const prompt = page.locator(promptSelector)
await expect(prompt).toBeVisible()
await prompt.fill("test")
await page.keyboard.press("Enter")
await withNoReplyPrompt(page, async () => {
await prompt.fill("test")
await page.keyboard.press("Enter")
})
// Wait for the URL to update with the new session ID
await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 15_000 }).not.toBe("")

View File

@ -9,6 +9,7 @@ import {
waitSession,
waitSessionSaved,
waitSlug,
withNoReplyPrompt,
} from "../actions"
import { promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
import { createSdk } from "../utils"
@ -58,8 +59,10 @@ async function createSessionFromWorkspace(
const prompt = page.locator(promptSelector)
await expect(prompt).toBeVisible()
await prompt.fill(text)
await page.keyboard.press("Enter")
await withNoReplyPrompt(page, async () => {
await prompt.fill(text)
await page.keyboard.press("Enter")
})
await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 15_000 }).not.toBe("")
const sessionID = sessionIDFromUrl(page.url())

View File

@ -8,11 +8,11 @@ import {
waitSession,
waitSessionIdle,
waitSlug,
withNoReplyPrompt,
} from "../actions"
import {
promptAgentSelector,
promptModelSelector,
promptSelector,
promptVariantSelector,
workspaceItemSelector,
workspaceNewSessionSelector,
@ -231,11 +231,14 @@ async function goto(page: Page, directory: string, sessionID?: string) {
}
async function submit(page: Page, value: string) {
const prompt = page.locator(promptSelector)
const prompt = page.locator('[data-component="prompt-input"]')
await expect(prompt).toBeVisible()
await prompt.click()
await prompt.fill(value)
await prompt.press("Enter")
await withNoReplyPrompt(page, async () => {
await prompt.click()
await prompt.fill(value)
await prompt.press("Enter")
})
await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 30_000 }).not.toBe("")
const id = sessionIDFromUrl(page.url())

View File

@ -0,0 +1,27 @@
import { describe, expect, test } from "bun:test"
import path from "node:path"
import { fileURLToPath } from "node:url"
const dir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../e2e")
function hasPrompt(src: string) {
if (!src.includes("withProject(")) return false
if (src.includes("withNoReplyPrompt(")) return false
if (src.includes("session.promptAsync({") && !src.includes("noReply: true")) return true
if (!src.includes("promptSelector")) return false
return src.includes('keyboard.press("Enter")') || src.includes('prompt.press("Enter")')
}
describe("e2e llm guard", () => {
test("withProject specs do not submit prompt replies", async () => {
const bad: string[] = []
for await (const file of new Bun.Glob("**/*.spec.ts").scan({ cwd: dir, absolute: true })) {
const src = await Bun.file(file).text()
if (!hasPrompt(src)) continue
bad.push(path.relative(dir, file))
}
expect(bad).toEqual([])
})
})