mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-11 07:54:52 +00:00
fix(desktop): clarify invalid browser input and capture failures
This commit is contained in:
parent
2d388ab6b3
commit
30dce5d30a
5 changed files with 25 additions and 9 deletions
|
|
@ -459,7 +459,7 @@ export function createBrowserPage(
|
|||
).catch((error) => {
|
||||
if (signal.aborted) throw error
|
||||
throw new Error(
|
||||
`browser.wait did not satisfy condition ${JSON.stringify(action.condition)} within ${action.timeoutMs ?? 10_000} ms. Inspect browser.snapshot({tabID}) and check text/frameID before retrying; timeoutMs can be increased up to 30000 for a genuinely slow page. Details: ${error instanceof Error ? error.message : String(error)}`,
|
||||
`browser.wait failed for condition ${JSON.stringify(action.condition)} (timeoutMs: ${action.timeoutMs ?? 10_000}). Inspect browser.snapshot({tabID}) and check text/frameID before retrying; timeoutMs can be increased up to 30000 for a genuinely slow page. Details: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
})
|
||||
break
|
||||
|
|
|
|||
|
|
@ -2,13 +2,16 @@ import type { Browser } from "@opencode-ai/plugin-browser/rpc"
|
|||
|
||||
export function protocolError(method: string, error: unknown) {
|
||||
const detail = message(error)
|
||||
const recovery = /(?:node|object).*(?:not found|not exist)|(?:find|resolve).*(?:node|object)|detached/i.test(detail)
|
||||
? "The element may have detached. Call browser.snapshot({tabID}) and use a fresh ref from that tab."
|
||||
: /context.*(?:destroyed|not found)|find.*context|session.*not found/i.test(detail)
|
||||
? "The document or frame changed. Call browser.frames({tabID}) and browser.snapshot({tabID}); use current frame IDs and refs."
|
||||
: /wasn't found|method not found|not implemented|not allowed/i.test(detail)
|
||||
? "This Chromium target does not support or allow the operation. Check desktop/plugin compatibility and report it; do not retry unchanged or disable browser security."
|
||||
: "Inspect browser.tabs.list({}) and the target tab before deciding to retry; a partially completed action is not automatically safe to repeat."
|
||||
const recovery =
|
||||
method === "DOM.setFileInputFiles" && /not.*file input/i.test(detail)
|
||||
? "Target is not a file input. Call browser.snapshot({tabID}) and choose an input[type=file] ref for browser.files.upload, or use browser.files.drop for a drop area."
|
||||
: /(?:node|object).*(?:not found|not exist)|(?:find|resolve).*(?:node|object)|detached/i.test(detail)
|
||||
? "The element may have detached. Call browser.snapshot({tabID}) and use a fresh ref from that tab."
|
||||
: /context.*(?:destroyed|not found)|find.*context|session.*not found/i.test(detail)
|
||||
? "The document or frame changed. Call browser.frames({tabID}) and browser.snapshot({tabID}); use current frame IDs and refs."
|
||||
: /wasn't found|method not found|not implemented|not allowed/i.test(detail)
|
||||
? "This Chromium target does not support or allow the operation. Check desktop/plugin compatibility and report it; do not retry unchanged or disable browser security."
|
||||
: "Inspect browser.tabs.list({}) and the target tab before deciding to retry; a partially completed action is not automatically safe to repeat."
|
||||
return new Error(`${recovery} Chromium command ${method} failed: ${detail}`, { cause: error })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export function createBrowserFiles() {
|
|||
await ready
|
||||
const file = this.add(name, mime)
|
||||
await writeFile(file.path, data).catch((error: unknown) => {
|
||||
file.state = "failed"
|
||||
throw new Error(
|
||||
"Cannot write the capture on the desktop. Ask the user to check desktop temporary-directory access and free space before retrying.",
|
||||
{ cause: error },
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ test("files distinguish pending, failed, unknown and missing desktop copies", as
|
|||
await expect(
|
||||
files.save("large.bin", "application/octet-stream", new Uint8Array(Browser.MAX_FILE_BYTES + 1)),
|
||||
).rejects.toThrow("Do not retry an identical capture")
|
||||
await expect(files.save(".", "text/plain", new Uint8Array([1]))).rejects.toThrow(
|
||||
"Cannot write the capture on the desktop",
|
||||
)
|
||||
expect(files.list().find((file) => file.name === ".")?.state).toBe("failed")
|
||||
} finally {
|
||||
await files.dispose()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,9 +131,11 @@ async function main() {
|
|||
const second = await call("tabs.open", { url: `${fixture}/other`, focus: false })
|
||||
assert.equal((await call("tabs.list", {})).tabs.length, 2)
|
||||
const tabID = first.id
|
||||
const upload = await rpc.write({ text: "server upload bytes" }, { location })
|
||||
await fails("trace.stop", { tabID }, /browser\.trace\.start/)
|
||||
await fails("cpu.stop", { tabID }, /browser\.cpu\.start/)
|
||||
await fails("evaluate", { tabID, frameID: "missing-frame", script: "1" }, /browser\.frames/)
|
||||
await fails("evaluate", { tabID, script: "throw new Error('fixture exception')" }, /Check the script and frameID/)
|
||||
await fails("press", { tabID, key: "ControlOrMeta+A" }, /Supported modifiers are Alt, Control, Meta, and Shift/)
|
||||
await fails("wait", { tabID, condition: "text" }, /requires non-empty text/)
|
||||
await fails("wait", { tabID, condition: "text", text: "not-on-the-page", timeoutMs: 1 }, /check text\/frameID/)
|
||||
|
|
@ -141,6 +143,7 @@ async function main() {
|
|||
await call("evaluate", {
|
||||
tabID,
|
||||
script: `(async () => {
|
||||
const radio=document.createElement('input'); radio.type='radio'; radio.checked=true; radio.setAttribute('aria-label','Selected radio'); document.querySelector('select').after(radio);
|
||||
const source=document.createElement('div'); source.draggable=true; source.tabIndex=0; source.setAttribute('role','button'); source.textContent='Drag source'; source.ondragstart=e=>e.dataTransfer.setData('text/plain','element dropped'); document.body.prepend(source);
|
||||
const drop=document.createElement('div'); drop.tabIndex=0; drop.setAttribute('role','button'); drop.textContent='Drop target'; drop.style.cssText='height:40px;width:300px;background:#ddd'; drop.ondragover=e=>e.preventDefault(); drop.ondrop=async e=>{e.preventDefault();document.querySelector('output').textContent=e.dataTransfer.files.length?await e.dataTransfer.files[0].text():e.dataTransfer.getData('text/plain')}; document.body.prepend(drop);
|
||||
await new Promise(resolve=>{const frame=document.querySelector('iframe'); frame.onload=()=>resolve(null); frame.src=${JSON.stringify(`http://localhost:${address.port}/frame`)};});
|
||||
|
|
@ -160,6 +163,10 @@ async function main() {
|
|||
}
|
||||
await call("fill", { tabID, ref: ref("Name"), text: "remote browser" })
|
||||
await fails("fill", { tabID, ref: ref("Apply"), text: "wrong target" }, /choose a textbox/)
|
||||
await fails("fill", { tabID, ref: ref("Upload"), text: "wrong target" }, /browser\.files\.upload/)
|
||||
await fails("check", { tabID, ref: ref("Selected radio"), checked: false }, /Select a different radio/)
|
||||
await call("check", { tabID, ref: ref("Selected radio"), checked: true })
|
||||
await fails("files.upload", { tabID, ref: ref("Apply"), paths: [upload] }, /choose an input\[type=file\] ref/)
|
||||
await fails("select", { tabID, ref: ref("Color"), values: ["missing-option"] }, /values are not visible labels/)
|
||||
await fails("click", { tabID, ref: "e999999999" }, /tab's newest snapshot/)
|
||||
await fails("screenshot", { tabID, ref: ref("Name"), fullPage: true }, /Remove the other argument/)
|
||||
|
|
@ -238,7 +245,6 @@ async function main() {
|
|||
const detail = await call("network.get", { tabID, id: network.requests[0].id, includeBody: true })
|
||||
assert.equal(detail.responseBody.state, "text")
|
||||
await fails("network.get", { tabID, id: "unknown-request" }, /Do not reload or resend/)
|
||||
const upload = await rpc.write({ text: "server upload bytes" }, { location })
|
||||
const fileSnap = await call("snapshot", { tabID })
|
||||
const input = fileSnap.content
|
||||
.split("\n")
|
||||
|
|
@ -285,7 +291,9 @@ async function main() {
|
|||
)
|
||||
await call("evaluate", { tabID, script: "setTimeout(()=>alert('hello dialog'),0); null" })
|
||||
await until(async () => (await call("dialog", { tabID, action: "get" })).dialog)
|
||||
await fails("evaluate", { tabID, script: "1" }, /Inspect it with browser\.dialog/)
|
||||
await call("dialog", { tabID, action: "dismiss" })
|
||||
await fails("dialog", { tabID, action: "accept" }, /no JavaScript dialog to handle/)
|
||||
await call("evaluate", { tabID, script: "window.open('/frame'); null" })
|
||||
await until(async () => (await call("tabs.list", {})).tabs.length === 3)
|
||||
await call("navigate", { tabID, url: `${fixture}/next` })
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue