feat(simulation): add literal screen text matching (#36085)

This commit is contained in:
James Long 2026-07-09 10:50:35 -04:00 committed by GitHub
parent aa0e626b12
commit 855a9569b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 45 additions and 0 deletions

View file

@ -104,6 +104,10 @@ export function state(harness: Harness) {
}
}
export function matches(harness: Pick<Harness, "screen">, text: string) {
return harness.screen().includes(text)
}
export async function screenshot(harness: Harness, name?: string) {
await harness.renderOnce()
const image = SimulationPng.screenshot(harness.renderer)

View file

@ -21,6 +21,8 @@ async function handle(
case "ui.state": {
return SimulationActions.state(harness)
}
case "ui.matches":
return SimulationActions.matches(harness, request.params.text)
case "ui.recording.finish":
if (!finishRecording) throw new Error("UI recording is not available")
return finishRecording()

View file

@ -97,12 +97,18 @@ export namespace Frontend {
export const RecordingFinish = Schema.String
export type RecordingFinish = Schema.Schema.Type<typeof RecordingFinish>
export const Matches = Schema.Boolean
export type Matches = Schema.Schema.Type<typeof Matches>
export const ScreenshotParams = Schema.Struct({ name: Schema.optional(Schema.String) })
export interface ScreenshotParams extends Schema.Schema.Type<typeof ScreenshotParams> {}
export const TypeParams = Schema.Struct({ text: Schema.String })
export interface TypeParams extends Schema.Schema.Type<typeof TypeParams> {}
export const MatchesParams = Schema.Struct({ text: Schema.String })
export interface MatchesParams extends Schema.Schema.Type<typeof MatchesParams> {}
export const PressParams = Schema.Struct({ key: Schema.String, modifiers: Schema.optional(KeyModifiers) })
export interface PressParams extends Schema.Schema.Type<typeof PressParams> {}
@ -121,6 +127,7 @@ export namespace Frontend {
Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.arrow"), params: ArrowParams }),
Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.focus"), params: FocusParams }),
Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.click"), params: ClickParams }),
Schema.Struct({ ...JsonRpc.RequestFields, method: Schema.Literal("ui.matches"), params: MatchesParams }),
Schema.Struct({
...JsonRpc.RequestFields,
method: Schema.Literal("ui.screenshot"),

View file

@ -0,0 +1,11 @@
import { expect, test } from "bun:test"
import { matches } from "../src/frontend/actions"
test("matches literal screen text", () => {
const harness = { screen: () => "OpenCode [ready].*" }
expect(matches(harness, "OpenCode")).toBe(true)
expect(matches(harness, "[ready].*")).toBe(true)
expect(matches(harness, "OpenCode.*ready")).toBe(false)
expect(matches(harness, "opencode")).toBe(false)
})

View file

@ -0,0 +1,21 @@
import { expect, test } from "bun:test"
import { Frontend } from "../src/protocol"
test("decodes ui.matches text params", () => {
expect(
Frontend.decodeRequest({
jsonrpc: "2.0",
id: 1,
method: "ui.matches",
params: { text: "OpenCode [ready].*" },
}),
).toMatchObject({ method: "ui.matches", params: { text: "OpenCode [ready].*" } })
expect(() =>
Frontend.decodeRequest({
jsonrpc: "2.0",
id: 1,
method: "ui.matches",
params: { pattern: "OpenCode.*" },
}),
).toThrow()
})