Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/desktop/src/ipc/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const PREVIEW_AUTOMATION_PRESS_CHANNEL = "desktop:preview-automation-pres
export const PREVIEW_AUTOMATION_SCROLL_CHANNEL = "desktop:preview-automation-scroll";
export const PREVIEW_AUTOMATION_EVALUATE_CHANNEL = "desktop:preview-automation-evaluate";
export const PREVIEW_AUTOMATION_WAIT_FOR_CHANNEL = "desktop:preview-automation-wait-for";
export const PREVIEW_AUTOMATION_SET_VIEWPORT_CHANNEL = "desktop:preview-automation-set-viewport";
export const PREVIEW_RECORDING_START_CHANNEL = "desktop:preview-recording-start";
export const PREVIEW_RECORDING_STOP_CHANNEL = "desktop:preview-recording-stop";
export const PREVIEW_RECORDING_SAVE_CHANNEL = "desktop:preview-recording-save";
Expand Down
22 changes: 19 additions & 3 deletions apps/desktop/src/ipc/methods/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {
DesktopPreviewAnnotationThemeInputSchema,
DesktopPreviewArtifactInputSchema,
DesktopPreviewAutomationClickInputSchema,
DesktopPreviewAutomationSetViewportInputSchema,
DesktopPreviewAutomationSnapshotInputSchema,
DesktopPreviewAutomationEvaluateInputSchema,
DesktopPreviewAutomationPressInputSchema,
DesktopPreviewAutomationScrollInputSchema,
Expand Down Expand Up @@ -276,11 +278,24 @@ export const automationStatus = DesktopIpc.makeIpcMethod({

export const automationSnapshot = DesktopIpc.makeIpcMethod({
channel: IpcChannels.PREVIEW_AUTOMATION_SNAPSHOT_CHANNEL,
payload: DesktopPreviewTabInputSchema,
payload: DesktopPreviewAutomationSnapshotInputSchema,
result: PreviewAutomationSnapshot,
handler: Effect.fn("desktop.ipc.preview.automationSnapshot")(function* ({ tabId }) {
handler: Effect.fn("desktop.ipc.preview.automationSnapshot")(function* ({ tabId, include }) {
const manager = yield* PreviewManager.PreviewManager;
return yield* manager.automationSnapshot(tabId, include ?? []);
}),
});

export const automationSetViewport = DesktopIpc.makeIpcMethod({
channel: IpcChannels.PREVIEW_AUTOMATION_SET_VIEWPORT_CHANNEL,
payload: DesktopPreviewAutomationSetViewportInputSchema,
result: Schema.Void,
handler: Effect.fn("desktop.ipc.preview.automationSetViewport")(function* (input) {
const manager = yield* PreviewManager.PreviewManager;
return yield* manager.automationSnapshot(tabId);
yield* manager.automationSetViewport(
input.tabId,
"clear" in input ? { clear: true } : { width: input.width, height: input.height },
);
}),
});

Expand Down Expand Up @@ -381,6 +396,7 @@ export const methods = [
closePictureInPicture,
automationStatus,
automationSnapshot,
automationSetViewport,
automationClick,
automationType,
automationPress,
Expand Down
12 changes: 10 additions & 2 deletions apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,16 @@ contextBridge.exposeInMainWorld("desktopBridge", {
automation: {
status: (tabId) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_AUTOMATION_STATUS_CHANNEL, { tabId }),
snapshot: (tabId) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_AUTOMATION_SNAPSHOT_CHANNEL, { tabId }),
snapshot: (tabId, include) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_AUTOMATION_SNAPSHOT_CHANNEL, {
tabId,
...(include === undefined ? {} : { include }),
}),
setViewport: (tabId, input) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_AUTOMATION_SET_VIEWPORT_CHANNEL, {
tabId,
...input,
}),
click: (tabId, input) =>
ipcRenderer.invoke(IpcChannels.PREVIEW_AUTOMATION_CLICK_CHANNEL, { tabId, input }),
type: (tabId, input) =>
Expand Down
29 changes: 29 additions & 0 deletions apps/desktop/src/preview/Manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2839,4 +2839,33 @@ describe("Preview automation diagnostics", () => {
expect(JSON.stringify(error)).not.toContain(selector);
expect("locator" in error).toBe(false);
});

it("names hidden, disabled, and ambiguous click failures without leaking the locator", () => {
const selector = "role=button[name='target-secret']";
const hidden = new PreviewManager.PreviewAutomationTargetHiddenError({
operation: "click",
tabId: "tab_1",
selectorKind: "locator",
selectorLength: selector.length,
});
const disabled = new PreviewManager.PreviewAutomationTargetDisabledError({
operation: "click",
tabId: "tab_1",
selectorKind: "locator",
selectorLength: selector.length,
});
const ambiguous = new PreviewManager.PreviewAutomationTargetAmbiguousError({
operation: "click",
tabId: "tab_1",
selectorKind: "locator",
selectorLength: selector.length,
matchCount: 3,
});
expect(hidden.message).toContain("not visible");
expect(disabled.message).toContain("disabled");
expect(ambiguous.message).toContain("matched 3 elements");
expect(hidden.message).not.toContain("secret");
expect(disabled.message).not.toContain("secret");
expect(ambiguous.message).not.toContain("secret");
});
});
Loading
Loading