Files
openidea/e2e/pythonInterpreters.spec.ts
T
Muuandarookieofc bff6a12e2f feat(python): manage every interpreter from a Python settings folder (#2987)
* feat(python): manage every interpreter from a Python settings folder

Settings › Languages & Frameworks now groups Python settings in one folder: All Interpreters (new), Dependencies (the project interpreter and its packages), Docstrings (split out of the interpreter page) and Ruff. All Interpreters lists interpreters detected on the machine, the ones other recently opened projects use and the ones added there, with a search field, PyCharm's "hide interpreters of other projects" and "show interpreter paths" toggles, and + / − to add an interpreter or remove it from the list (hidden, never deleted; a removed project interpreter falls back to the project virtualenv).

Each interpreter's source has its own icon (virtualenv, conda, uv, Poetry, pyenv/mise/asdf, system) in the list, the Add Interpreter dialog, the interpreter combo and the status bar menu. The IDE-wide list and its view options are global settings that a project cannot override; removed interpreters also disappear from the interpreter combo and the status bar popup.

* fix(python): compare interpreter paths by their own case rules and cap probes

A POSIX interpreter path kept only its own case (Linux allows `/opt/Python` and
`/opt/python` side by side); only Windows drive/UNC paths are folded now, and the
status bar uses the same key as the list. A configured `~/…` interpreter is matched
against the absolute path the backend resolves it to, so the "Project interpreter"
badge and removing it in use keep working without the frontend knowing the home
directory.

`sdk_python_project_interpreters` probes at most four recent projects at a time
instead of one thread per project, so opening All Interpreters does not start ~30
pythons at once. The hidden-but-in-use interpreter stays selectable in the combo and
status popup (custom entry / current fallback); that is now documented on `exclude`.

---------

Co-authored-by: arookieofc <arookieofc@gmail.com>
2026-11-13 08:56:17 +08:00

95 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test, stubDesktop } from "./fixtures";
import type { Page } from "@playwright/test";
/** Settings › Languages & Frameworks › Python › All Interpreters over a stubbed backend. */
async function interpretersPage(page: Page) {
await page.evaluate(async () => {
const projectModule = "/src/platform/project.ts";
const { useProject } = await import(/* @vite-ignore */ projectModule);
const project = useProject.getState().project!;
const current = useProject.getState().settings ?? {};
const settings = {
...current,
languages: {
...(current.languages ?? {}),
python: {
interpreter: "/project/.venv/bin/python",
activateVirtualenv: true,
interpreters: ["/envs/tools/bin/python"],
hiddenInterpreters: ["/usr/bin/python3.9"],
hideOtherProjectInterpreters: false,
showInterpreterPaths: true,
},
},
};
useProject.setState({ settings });
const sdk = (path: string, name: string, home: string | null, source: string) => ({
path,
name,
version: "3.12.0",
source,
home,
extra: null,
});
window.__ipc.handlers.settings_get = () => settings;
window.__ipc.handlers.settings_get_global = () => settings;
window.__ipc.handlers.project_current = () => ({ ...project, settings });
window.__ipc.handlers.sdk_detect = () => [
sdk("/usr/bin/python3", "Python 3.12", "/usr", "system"),
sdk("/usr/bin/python3.9", "Python 3.9", "/usr", "system"),
sdk("/project/.venv/bin/python", "Python 3.12 (.venv)", "/project/.venv", "venv"),
];
window.__ipc.handlers.sdk_python_project_interpreters = () => [
{
project: "/apps/shop",
sdk: sdk("/apps/shop/.venv/bin/python", "Python 3.11 (shop)", "/apps/shop/.venv", "venv"),
},
];
window.__ipc.handlers.sdk_python_interpreter = () =>
sdk("/envs/tools/bin/python", "Python 3.13 (tools)", "/envs/tools", "venv");
const actions = "/src/platform/actions.ts";
void (await import(/* @vite-ignore */ actions)).executeAction("file.settings", "python.interpreters");
});
const dialog = page.getByRole("dialog", { name: "Settings" });
await expect(dialog).toBeVisible();
return dialog;
}
test("lists interpreters with their project, filters and toggles paths", async ({ workbench: page }) => {
await stubDesktop(page);
const dialog = await interpretersPage(page);
const row = (name: RegExp) => dialog.getByRole("option", { name });
await expect(row(/Python 3\.11 \(shop\)/)).toBeVisible();
await expect(row(/Python 3\.13 \(tools\)/)).toBeVisible();
// Removed earlier: not listed even though detection finds it.
await expect(row(/Python 3\.9/)).toHaveCount(0);
await expect(dialog.getByText("Project interpreter")).toBeVisible();
await expect(dialog.getByText("/project/.venv/bin/python")).toBeVisible();
await dialog.getByRole("button", { name: "Hide Interpreters from Other Projects" }).click();
await expect(row(/Python 3\.11 \(shop\)/)).toHaveCount(0);
await expect(row(/Python 3\.12 \(\.venv\)/)).toBeVisible();
await dialog.getByRole("button", { name: "Show Interpreter Paths" }).click();
await expect(dialog.getByText("/project/.venv/bin/python")).toHaveCount(0);
});
test("removes an interpreter in use and drops it from the interpreter combo", async ({ workbench: page }) => {
await stubDesktop(page);
const dialog = await interpretersPage(page);
await dialog.getByRole("option", { name: /Python 3\.12 \(\.venv\)/ }).click();
await dialog.getByRole("button", { name: "Remove Interpreter" }).click();
const confirm = page.getByRole("dialog", { name: "Remove Interpreter" });
await expect(confirm).toBeVisible();
await confirm.getByRole("button", { name: "Remove" }).click();
await expect(dialog.getByRole("option", { name: /Python 3\.12 \(\.venv\)/ })).toHaveCount(0);
// The project interpreter page offers what the list keeps: no removed path, added environments included.
await dialog.getByRole("treeitem", { name: "Dependencies", exact: true }).click();
await dialog.getByRole("button", { name: "Python interpreter" }).click();
await expect(page.getByRole("option", { name: /Python 3\.13 \(tools\)/ })).toBeVisible();
await expect(page.getByRole("option", { name: /Python 3\.9/ })).toHaveCount(0);
});