test(ui): pick select options by role instead of by text

Clicking a Base UI select entry found by text or by a title attribute is a
race. The text node exists one render before the popup finishes entering,
and until then the positioner still carries pointer-events: none, so
user-event refuses the click and the test throws. Querying by role only
matches once the popup is exposed to the accessibility tree, which is after
that window closes.

Route the 37 remaining select interactions through chooseSelectOption, which
does the role query. Instrumenting the converted files shows the text query
resolving while the popup was still pointer-blocked on 6 of 41 samples; the
role query was never blocked.

Seven files kept their text queries because their popup entries carry no
accessible role, so there is nothing to query by.
This commit is contained in:
Yuneng Jiang 2026-09-01 12:42:27 -07:00
parent a5f9410681
commit eb53639ecb
No known key found for this signature in database
19 changed files with 63 additions and 84 deletions

View file

@ -5,6 +5,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import AddAgentForm from "./add_agent_form";
import * as networking from "@/components/networking";
import type { AgentCreateInfo } from "@/components/networking";
import { chooseSelectOption } from "../../../../../tests/test-utils";
vi.mock("@/components/networking", () => ({
createAgentCall: vi.fn(),
@ -309,8 +310,7 @@ describe("AddAgentForm submit payload", () => {
await user.type(await screen.findByLabelText("Allowed Models"), "gpt-4o,");
await user.keyboard("{Escape}");
await user.click(screen.getByLabelText("Allowed Agents (Sub-Agents)"));
await user.click(await screen.findByTitle("Sub Agent One"));
await chooseSelectOption(user, screen.getByLabelText("Allowed Agents (Sub-Agents)"), "Sub Agent One");
await user.keyboard("{Escape}");
await user.click(screen.getByText(/Configure which models, agents, and MCP tools/));
await user.click(screen.getByRole("button", { name: /^Next/ }));

View file

@ -4,6 +4,7 @@ import React from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import BudgetModal from "./budget_modal";
import { chooseSelectOption } from "../../../../../tests/test-utils";
const { createMock } = vi.hoisted(() => ({ createMock: vi.fn() }));
@ -63,8 +64,7 @@ describe("BudgetModal", () => {
await openOptionalSettings(user);
fireEvent.change(screen.getByLabelText("Max Budget (USD)"), { target: { value: "42.567" } });
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("monthly"));
await chooseSelectOption(user, screen.getByRole("combobox"), "monthly");
await create(user);
@ -80,8 +80,7 @@ describe("BudgetModal", () => {
await openOptionalSettings(user);
fireEvent.change(screen.getByLabelText("Max Budget (USD)"), { target: { value: "42.567" } });
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("monthly"));
await chooseSelectOption(user, screen.getByRole("combobox"), "monthly");
await user.click(screen.getByText("Optional Settings"));
await waitFor(() => expect(screen.queryByLabelText("Max Budget (USD)")).not.toBeInTheDocument());

View file

@ -6,6 +6,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import type { components } from "@/lib/http/schema";
import EditBudgetModal from "./edit_budget_modal";
import { chooseSelectOption } from "../../../../../tests/test-utils";
const { updateMock } = vi.hoisted(() => ({ updateMock: vi.fn() }));
@ -73,8 +74,7 @@ describe("EditBudgetModal", () => {
await user.clear(screen.getByLabelText("Max Budget (USD)"));
fireEvent.change(screen.getByLabelText("Max Budget (USD)"), { target: { value: "42.567" } });
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("monthly"));
await chooseSelectOption(user, screen.getByRole("combobox"), "monthly");
await save(user);

View file

@ -5,6 +5,7 @@ import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "@/../tests/test-utils";
import CoordinationRedisTypeSelector from "./CoordinationRedisTypeSelector";
import { COORDINATION_REDIS_TYPE_DESCRIPTIONS } from "./coordinationRedisFields";
import { chooseSelectOption } from "../../../../../../tests/test-utils";
describe("CoordinationRedisTypeSelector", () => {
it("labels the control and shows the current selection", () => {
@ -36,8 +37,7 @@ describe("CoordinationRedisTypeSelector", () => {
const user = userEvent.setup();
renderWithProviders(<CoordinationRedisTypeSelector redisType="node" onTypeChange={onTypeChange} />);
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("Cluster"));
await chooseSelectOption(user, screen.getByRole("combobox"), "Cluster");
expect(onTypeChange).toHaveBeenCalledTimes(1);
expect(onTypeChange.mock.calls[0][0]).toBe("cluster");

View file

@ -97,6 +97,7 @@ import {
useStopShadowEval,
type ShadowEvalJob,
} from "./useShadowEval";
import { chooseSelectOption } from "../../../../../tests/test-utils";
const job = (overrides: Partial<ShadowEvalJob> = {}): ShadowEvalJob => ({
job_id: "job-1",
@ -437,8 +438,7 @@ describe("ShadowEvalSection", () => {
await user.click(within(keyList).getByText("prod-alpha"));
await user.click(keyInput);
await user.click(within(keyList).getByText("staging-beta"));
await user.click(screen.getByPlaceholderText("Select up to 4 auto-routers"));
await user.click(await screen.findByText("gpt-auto"));
await chooseSelectOption(user, screen.getByPlaceholderText("Select up to 4 auto-routers"), "gpt-auto");
expect(screen.getByText("Start shadow eval")).toBeDisabled();
@ -470,8 +470,7 @@ describe("ShadowEvalSection", () => {
await user.click(screen.getByPlaceholderText("Search teams by alias"));
const teamList = await screen.findByTestId("paginated-multi-select-list");
await user.click(within(teamList).getByText("engineering"));
await user.click(screen.getByPlaceholderText("Select up to 4 auto-routers"));
await user.click(await screen.findByText("gpt-auto"));
await chooseSelectOption(user, screen.getByPlaceholderText("Select up to 4 auto-routers"), "gpt-auto");
await user.click(screen.getByPlaceholderText("Select a judge model"));
await user.click(await screen.findByRole("option", { name: /anthropic\/claude-sonnet-5/ }));
await user.click(screen.getByText("Start shadow eval"));
@ -502,8 +501,7 @@ describe("ShadowEvalSection", () => {
await user.click(screen.getByPlaceholderText("Search keys by alias"));
const keyList = await screen.findByTestId("paginated-multi-select-list");
await user.click(within(keyList).getByText("prod-alpha"));
await user.click(screen.getByPlaceholderText("Select up to 4 auto-routers"));
await user.click(await screen.findByText("gpt-auto"));
await chooseSelectOption(user, screen.getByPlaceholderText("Select up to 4 auto-routers"), "gpt-auto");
await user.click(screen.getByPlaceholderText("Select a judge model"));
await user.click(await screen.findByRole("option", { name: /anthropic\/claude-sonnet-5/ }));

View file

@ -6,6 +6,7 @@ import { describe, expect, it, vi } from "vitest";
import { ToolTestPanel } from "./ToolTestPanel";
import { InputSchema, MCPTool } from "@/components/mcp_tools/types";
import { chooseSelectOption } from "../../../../../tests/test-utils";
const buildTool = (schema: InputSchema | string): MCPTool => ({
name: "demo-tool",
@ -229,8 +230,7 @@ describe("ToolTestPanel argument payload", () => {
const onSubmit = await submitPanel(
{ type: "object", properties: { active: { type: "boolean", default: false } } },
async (user) => {
await user.click(screen.getByLabelText("active"));
await user.click(await screen.findByText("True"));
await chooseSelectOption(user, screen.getByLabelText("active"), "True");
},
);

View file

@ -5,6 +5,7 @@ import userEvent, { PointerEventsCheckLevel } from "@testing-library/user-event"
import { renderWithProviders } from "@/../tests/test-utils";
import * as networking from "@/components/networking";
import PolicyTestPanel from "./policy_test_panel";
import { chooseSelectOption } from "../../../../../tests/test-utils";
vi.mock("@/components/networking");
@ -24,8 +25,7 @@ const setup = () => {
};
const pickOption = async (user: ReturnType<typeof userEvent.setup>, label: string, option: string) => {
await user.click(screen.getByLabelText(label));
await user.click(await screen.findByTitle(option));
await chooseSelectOption(user, screen.getByLabelText(label), option);
};
const simulate = async (user: ReturnType<typeof userEvent.setup>) => {

View file

@ -5,6 +5,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import { deletePromptCall, getPromptsList } from "@/components/networking";
import PromptsPanel from "./index";
import { chooseSelectOption } from "../../../../../tests/test-utils";
vi.mock("@/components/networking", () => ({
getPromptsList: vi.fn(),
@ -118,8 +119,7 @@ describe("PromptsPanel toolbar", () => {
expect(screen.getByText("All Environments")).toBeInTheDocument();
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("Production"));
await chooseSelectOption(user, screen.getByRole("combobox"), "Production");
await waitFor(() => expect(mockGetPromptsList).toHaveBeenLastCalledWith("sk-test", "production"));
});
@ -131,12 +131,10 @@ describe("PromptsPanel toolbar", () => {
renderPanel("Admin");
await screen.findByText("table-loaded");
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("Production"));
await chooseSelectOption(user, screen.getByRole("combobox"), "Production");
await waitFor(() => expect(screen.getByRole("combobox")).toHaveTextContent("Production"));
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("All Environments"));
await chooseSelectOption(user, screen.getByRole("combobox"), "All Environments");
await waitFor(() => expect(screen.getByRole("combobox")).toHaveTextContent("All Environments"));
await waitFor(() => expect(mockGetPromptsList).toHaveBeenLastCalledWith("sk-test", undefined));

View file

@ -2,7 +2,7 @@ import { fireEvent, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, MockedFunction, vi } from "vitest";
import { renderWithProviders } from "../../../tests/test-utils";
import { chooseSelectOption, renderWithProviders } from "../../../tests/test-utils";
import { Team } from "../key_team_helpers/key_list";
import { TeamsResponse, useTeamsTable } from "@/app/(dashboard)/hooks/teams/useTeams";
import { TeamsTable } from "./TeamsTable";
@ -243,8 +243,7 @@ describe("row actions", () => {
await user.click(await screen.findByText("Edit team"));
expect(onEditTeam).toHaveBeenCalledWith(expect.objectContaining({ team_id: "team-1" }));
await user.click(screen.getByTestId("team-actions-team-1"));
await user.click(await screen.findByText("Delete team"));
await chooseSelectOption(user, screen.getByTestId("team-actions-team-1"), "Delete team", "menuitem");
expect(onDeleteTeam).toHaveBeenCalledWith(expect.objectContaining({ team_id: "team-1" }));
});

View file

@ -2,7 +2,7 @@ import { screen, waitFor, within, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import type { OnUrlUpdateFunction } from "nuqs/adapters/testing";
import { vi, it, expect, beforeEach, describe, Mock, MockedFunction } from "vitest";
import { renderWithProviders } from "../../../tests/test-utils";
import { chooseSelectOption, renderWithProviders } from "../../../tests/test-utils";
import { VirtualKeysTable } from "./VirtualKeysTable";
import { KeyResponse, Team } from "../key_team_helpers/key_list";
import { useKeyInfo } from "@/app/(dashboard)/hooks/keys/useKeyInfo";
@ -327,8 +327,7 @@ it("sorts by the backend max_budget field when 'Budget descending' is chosen fro
const user = userEvent.setup();
renderWithProviders(<VirtualKeysTable />);
await user.click(screen.getByTestId("sort-trigger-spend"));
await user.click(await screen.findByText("Budget descending"));
await chooseSelectOption(user, screen.getByTestId("sort-trigger-spend"), "Budget descending", "menuitem");
await waitFor(() => {
expect(mockUseKeys).toHaveBeenLastCalledWith(
@ -343,8 +342,7 @@ it("emphasizes the active field in the Spend / Budget header so the sorted colum
const user = userEvent.setup();
renderWithProviders(<VirtualKeysTable />);
await user.click(screen.getByTestId("sort-trigger-spend"));
await user.click(await screen.findByText("Budget descending"));
await chooseSelectOption(user, screen.getByTestId("sort-trigger-spend"), "Budget descending", "menuitem");
await waitFor(() => {
expect(screen.getByText("Budget", { selector: "[data-sort-field='max_budget']" })).toHaveClass("font-semibold");
@ -356,8 +354,7 @@ it("sorts by spend ascending when 'Spend ascending' is chosen from the Spend / B
const user = userEvent.setup();
renderWithProviders(<VirtualKeysTable />);
await user.click(screen.getByTestId("sort-trigger-spend"));
await user.click(await screen.findByText("Spend ascending"));
await chooseSelectOption(user, screen.getByTestId("sort-trigger-spend"), "Spend ascending", "menuitem");
await waitFor(() => {
expect(mockUseKeys).toHaveBeenLastCalledWith(1, 50, expect.objectContaining({ sortBy: "spend", sortOrder: "asc" }));

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import userEvent, { PointerEventsCheckLevel } from "@testing-library/user-event";
import { fireEvent, renderWithProviders, screen, waitFor } from "../../tests/test-utils";
import { chooseSelectOption, fireEvent, renderWithProviders, screen, waitFor } from "../../tests/test-utils";
import AddPassThroughEndpoint from "./add_pass_through";
const createPassThroughEndpoint = vi.fn();
@ -153,8 +153,7 @@ describe("add_pass_through submit payload", () => {
await openModal(user);
await fillRequiredFields(user);
await user.click(screen.getByLabelText(/HTTP Methods/));
await user.click(await screen.findByTitle("POST"));
await chooseSelectOption(user, screen.getByLabelText(/HTTP Methods/), "POST");
await submit(user);

View file

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import userEvent, { PointerEventsCheckLevel } from "@testing-library/user-event";
import { describe, expect, it, vi, beforeEach } from "vitest";
import { fireEvent, renderWithProviders, screen, waitFor } from "../../../tests/test-utils";
import { chooseSelectOption, fireEvent, renderWithProviders, screen, waitFor } from "../../../tests/test-utils";
import KeyLifecycleSettings from "./KeyLifecycleSettings";
const CREATE_PLACEHOLDER = "e.g., 30d or leave empty to never expire";
@ -167,8 +167,7 @@ describe("KeyLifecycleSettings", () => {
await user.click(screen.getByRole("switch"));
expect(await screen.findByText("Rotation Interval")).toBeInTheDocument();
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("90 days"));
await chooseSelectOption(user, screen.getByRole("combobox"), "90 days");
await waitFor(() => expect(screen.getAllByTitle("90 days").some(isRenderedSelection)).toBe(true));
expect(screen.getByTestId("rotation-interval-value")).toHaveTextContent("90d");
@ -181,8 +180,7 @@ describe("KeyLifecycleSettings", () => {
await user.click(screen.getByRole("switch"));
expect(await screen.findByText("Rotation Interval")).toBeInTheDocument();
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("Custom interval"));
await chooseSelectOption(user, screen.getByRole("combobox"), "Custom interval");
expect(await screen.findByPlaceholderText("e.g., 1s, 5m, 2h, 14d")).toBeInTheDocument();
expect(screen.getByText("Supported formats: seconds (s), minutes (m), hours (h), days (d)")).toBeInTheDocument();
@ -196,8 +194,7 @@ describe("KeyLifecycleSettings", () => {
await user.click(screen.getByRole("switch"));
expect(await screen.findByText("Rotation Interval")).toBeInTheDocument();
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("Custom interval"));
await chooseSelectOption(user, screen.getByRole("combobox"), "Custom interval");
const customInput = await screen.findByPlaceholderText("e.g., 1s, 5m, 2h, 14d");
fireEvent.change(customInput, { target: { value: "14d" } });
@ -213,14 +210,12 @@ describe("KeyLifecycleSettings", () => {
await user.click(screen.getByRole("switch"));
expect(await screen.findByText("Rotation Interval")).toBeInTheDocument();
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("Custom interval"));
await chooseSelectOption(user, screen.getByRole("combobox"), "Custom interval");
const customInput = await screen.findByPlaceholderText("e.g., 1s, 5m, 2h, 14d");
fireEvent.change(customInput, { target: { value: "14d" } });
await waitFor(() => expect(screen.getByTestId("rotation-interval-value")).toHaveTextContent("14d"));
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("7 days"));
await chooseSelectOption(user, screen.getByRole("combobox"), "7 days");
await waitFor(() => expect(screen.getByTestId("rotation-interval-value")).toHaveTextContent("7d"));
expect(screen.queryByPlaceholderText("e.g., 1s, 5m, 2h, 14d")).not.toBeInTheDocument();

View file

@ -2,6 +2,7 @@ import { act, fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
import ModelSelector from "./ModelSelector";
import { chooseSelectOption } from "../../../tests/test-utils";
vi.mock("@/components/llm_calls/fetch_models", () => ({
fetchAvailableModels: vi.fn().mockResolvedValue([]),
@ -9,8 +10,7 @@ vi.mock("@/components/llm_calls/fetch_models", () => ({
const openCustomModelInput = async () => {
const user = userEvent.setup();
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("Enter custom model"));
await chooseSelectOption(user, screen.getByRole("combobox"), "Enter custom model");
return screen.getByPlaceholderText("Enter custom model name");
};

View file

@ -7,6 +7,7 @@ import { describe, expect, it, vi } from "vitest";
import { DataTable } from "./DataTable";
import { DataTableMultiSortHeader, DataTableSortHeader } from "./DataTableSortHeader";
import { DataTableViewOptions } from "./DataTableViewOptions";
import { chooseSelectOption } from "../../../../tests/test-utils";
interface Person {
id: string;
@ -176,16 +177,13 @@ describe("DataTable sorting", () => {
const user = userEvent.setup();
render(<DataTable data={CHARLIE_ALICE_BOB} columns={dropdownSortColumns} sortingMode="client" />);
await user.click(screen.getByTestId("sort-trigger-name"));
await user.click(await screen.findByText("Ascending"));
await chooseSelectOption(user, screen.getByTestId("sort-trigger-name"), "Ascending", "menuitem");
expect(names()).toEqual(["Alice", "Bob", "Charlie"]);
await user.click(screen.getByTestId("sort-trigger-name"));
await user.click(await screen.findByText("Descending"));
await chooseSelectOption(user, screen.getByTestId("sort-trigger-name"), "Descending", "menuitem");
expect(names()).toEqual(["Charlie", "Bob", "Alice"]);
await user.click(screen.getByTestId("sort-trigger-name"));
await user.click(await screen.findByText("Reset"));
await chooseSelectOption(user, screen.getByTestId("sort-trigger-name"), "Reset", "menuitem");
expect(names()).toEqual(["Charlie", "Alice", "Bob"]);
});
@ -202,12 +200,10 @@ describe("DataTable sorting", () => {
/>,
);
await user.click(screen.getByTestId("sort-trigger-spend"));
await user.click(await screen.findByText("Budget descending"));
await chooseSelectOption(user, screen.getByTestId("sort-trigger-spend"), "Budget descending", "menuitem");
expect(onSortingChange).toHaveBeenLastCalledWith([{ id: "max_budget", desc: true }]);
await user.click(screen.getByTestId("sort-trigger-spend"));
await user.click(await screen.findByText("Spend ascending"));
await chooseSelectOption(user, screen.getByTestId("sort-trigger-spend"), "Spend ascending", "menuitem");
expect(onSortingChange).toHaveBeenLastCalledWith([{ id: "spend", desc: false }]);
});

View file

@ -13,6 +13,7 @@ import { useState } from "react";
import { describe, expect, it, vi } from "vitest";
import { DataTableSortHeader, type DataTableSortVariant } from "./DataTableSortHeader";
import { chooseSelectOption } from "../../../../tests/test-utils";
interface Item {
name: string;
@ -84,16 +85,13 @@ describe("DataTableSortHeader", () => {
const user = userEvent.setup();
render(<SortHeaderHarness variant="dropdown-tristate" />);
await user.click(screen.getByTestId("sort-trigger-name"));
await user.click(await screen.findByText("Descending"));
await chooseSelectOption(user, screen.getByTestId("sort-trigger-name"), "Descending", "menuitem");
expect(screen.getByTestId("sort-trigger-name").querySelector('[data-sort-indicator="desc"]')).not.toBeNull();
await user.click(screen.getByTestId("sort-trigger-name"));
await user.click(await screen.findByText("Ascending"));
await chooseSelectOption(user, screen.getByTestId("sort-trigger-name"), "Ascending", "menuitem");
expect(screen.getByTestId("sort-trigger-name").querySelector('[data-sort-indicator="asc"]')).not.toBeNull();
await user.click(screen.getByTestId("sort-trigger-name"));
await user.click(await screen.findByText("Reset"));
await chooseSelectOption(user, screen.getByTestId("sort-trigger-name"), "Reset", "menuitem");
expect(screen.getByTestId("sort-trigger-name").querySelector('[data-sort-indicator="none"]')).not.toBeNull();
});

View file

@ -5,6 +5,7 @@ import { describe, expect, it, vi } from "vitest";
import { PaginatedSearchSelect } from "./PaginatedSearchSelect";
import type { SearchSelectOption } from "./SearchSelect";
import { chooseSelectOption } from "../../../tests/test-utils";
const OPTIONS: SearchSelectOption[] = [
{ label: "alias-alpha", value: "alias-alpha" },
@ -63,8 +64,7 @@ describe("PaginatedSearchSelect", () => {
}
render(<Controlled />);
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("alias-beta"));
await chooseSelectOption(user, screen.getByRole("combobox"), "alias-beta");
expect(screen.getByRole("combobox")).toHaveValue("alias-beta");
await new Promise((resolve) => setTimeout(resolve, 400));
@ -136,8 +136,7 @@ describe("PaginatedSearchSelect", () => {
const onValueChange = vi.fn();
renderSelect({ onValueChange });
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("alias-beta"));
await chooseSelectOption(user, screen.getByRole("combobox"), "alias-beta");
expect(onValueChange).toHaveBeenCalledWith("alias-beta");
});
@ -255,8 +254,7 @@ describe("PaginatedSearchSelect", () => {
}
render(<Refetching />);
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("Beta Team"));
await chooseSelectOption(user, screen.getByRole("combobox"), "Beta Team");
await user.click(screen.getByRole("button", { name: "refetch" }));
expect(screen.getByRole("combobox")).toHaveValue("Beta Team");

View file

@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { SearchSelect } from "./SearchSelect";
import { chooseSelectOption } from "../../../tests/test-utils";
const OPTIONS = [
{ label: "Acme Prod", value: "team-1" },
@ -71,8 +72,7 @@ describe("SearchSelect", () => {
const onValueChange = vi.fn();
const user = userEvent.setup();
render(<SearchSelect options={OPTIONS} onValueChange={onValueChange} />);
await user.click(screen.getByRole("combobox"));
await user.click(await screen.findByText("Growth"));
await chooseSelectOption(user, screen.getByRole("combobox"), "Growth");
expect(onValueChange).toHaveBeenCalledWith("team-2");
});

View file

@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event";
import { useState } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { renderWithProviders, testQueryClient } from "../../../tests/test-utils";
import { chooseSelectOption, renderWithProviders, testQueryClient } from "../../../tests/test-utils";
import { ERROR_CODE_OPTIONS } from "./constants";
import { LOG_FILTER_IDS } from "./log_filter_logic";
import { RequestLogsFilters } from "./RequestLogsFilters";
@ -125,8 +125,7 @@ describe("RequestLogsFilters", () => {
const user = userEvent.setup();
const { set } = renderFilters();
await user.click(await screen.findByPlaceholderText("Search an internal user"));
await user.click(await screen.findByText("alice@example.com"));
await chooseSelectOption(user, await screen.findByPlaceholderText("Search an internal user"), "alice@example.com");
expect(set).toHaveBeenCalledWith(LOG_FILTER_IDS.USER_ID, "alice@example.com");
});

View file

@ -45,19 +45,22 @@ const pointerBlocked = (element: HTMLElement): boolean => {
};
/**
* Opens a Base UI Select and picks an option by its accessible name.
* Opens a Base UI popup and picks an entry by its accessible name.
*
* The option is in the DOM one render before the popup finishes entering, and until then its
* positioner still carries `pointer-events: none`, which user-event refuses to click. Waiting on
* the option text alone is a race that React 19's flush timing loses.
* Querying the entry by text or by a title attribute matches the moment the node exists, which is
* one render before the popup finishes entering. Until then the positioner still carries
* `pointer-events: none` and user-event refuses to click, so that shape is a race a fast machine
* loses. The role query only matches once the popup is open to the accessibility tree, which is
* what makes this wait correct rather than lucky.
*/
export const chooseSelectOption = async (
user: Pick<ReturnType<typeof userEvent.setup>, "click">,
trigger: HTMLElement,
optionName: string | RegExp,
role: "option" | "menuitem" | "menuitemradio" = "option",
) => {
await user.click(trigger);
const option = await screen.findByRole("option", { name: optionName });
const option = await screen.findByRole(role, { name: optionName });
await waitFor(() => expect(pointerBlocked(option)).toBe(false));
await user.click(option);
};