diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index e30e5437a..8d3a17917 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1185,19 +1185,11 @@ async function executeCommand( } case "select": { const [selector, values] = args as [string, string[]]; - if (!stagehand) { - throw new Error("Stagehand instance not available"); - } const resolved = resolveSelector(selector); - // selectOption takes the first value as argument - const action = { - selector: resolved, - description: "select option", - method: "selectOption", - arguments: [values[0] || ""], - }; - await stagehand.act(action); - return { selected: values }; + const selected = await page! + .deepLocator(resolved) + .selectOption(values.length === 1 ? values[0] || "" : values); + return { selected }; } case "upload": { const [selector, filePaths] = args as [string, string[]]; diff --git a/packages/cli/tests/cli.test.ts b/packages/cli/tests/cli.test.ts index 08337fbf7..2fc508c20 100644 --- a/packages/cli/tests/cli.test.ts +++ b/packages/cli/tests/cli.test.ts @@ -314,6 +314,22 @@ describe("Browse CLI", () => { expect(data.typed).toBe(true); }); + it("should select multiple values in a multi-select element", async () => { + const html = ``; + const dataUrl = `data:text/html,${encodeURIComponent(html)}`; + + await browse(`open "${dataUrl}"`); + const result = await browse("select select red blue"); + const data = parseJson<{ selected: string[] }>(result.stdout); + expect(data.selected).toEqual(["red", "blue"]); + + const selectedResult = await browse( + 'eval "Array.from(document.querySelector(\\"select\\").selectedOptions).map(o=>o.value).join(\\\",\\\")"', + ); + const selectedData = parseJson<{ result: string }>(selectedResult.stdout); + expect(selectedData.result).toBe("red,blue"); + }); + it("should press keys", async () => { await browse("open https://example.com"); const result = await browse("press Tab"); diff --git a/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts b/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts index 6c966f063..fb4a9169a 100644 --- a/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts +++ b/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts @@ -146,8 +146,10 @@ const METHOD_HANDLER_MAP: Record< export async function selectOption(ctx: UnderstudyMethodHandlerContext) { const { locator, xpath, args } = ctx; try { - const text = args[0]?.toString() || ""; - await locator.selectOption(text); + const values = args.map((arg) => arg.toString()); + await locator.selectOption( + values.length <= 1 ? (values[0] ?? "") : values, + ); } catch (e) { const msg = e instanceof Error ? e.message : String(e); const stack = e instanceof Error ? e.stack : undefined; diff --git a/packages/core/tests/integration/perform-understudy-method.spec.ts b/packages/core/tests/integration/perform-understudy-method.spec.ts index 4e8ae2ef4..e25165c3b 100644 --- a/packages/core/tests/integration/perform-understudy-method.spec.ts +++ b/packages/core/tests/integration/perform-understudy-method.spec.ts @@ -98,6 +98,39 @@ test.describe("tests performUnderstudyMethod", () => { expect(inputValue).toBe("Smog Check Technician"); }); + test("tests selecting multiple options from a multi-select dropdown", async () => { + const page = v3.context.pages()[0]; + await page.goto( + "data:text/html," + + encodeURIComponent( + ` + + `, + ), + ); + + await performUnderstudyMethod( + page, + page.mainFrame(), + "selectOptionFromDropdown", + "xpath=//*[@id='colors']", + ["red", "blue"], + 30000, + ); + + const selectedValues = await page.evaluate(() => { + const select = document.getElementById("colors") as + | HTMLSelectElement + | null; + return Array.from(select?.selectedOptions ?? []).map((o) => o.value); + }); + expect(selectedValues).toEqual(["red", "blue"]); + }); + test("tests drag & drop works (start xpath & end xpath)", async () => { const page = v3.context.pages()[0]; await page.goto(