From db1deb161752146b1431620b74357454891b02a5 Mon Sep 17 00:00:00 2001 From: Chris Dryden <117657238+chrisdrydenaltmetric@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:07:01 +0100 Subject: [PATCH] Merge pull request #33895 from overleaf/cd-pyodide-unsupported-module-message Improve error messaging for python modules unsupported by Pyodide GitOrigin-RevId: 038c672ad9da46ea6d4640b8ed37426c92d22e72 --- .../editor/python/pyodide.worker.ts | 16 ++++++- .../components/python-output-pane.spec.tsx | 42 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts b/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts index 38d8ca577e..0c942b819e 100644 --- a/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts +++ b/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts @@ -31,6 +31,15 @@ function classifyErrorType(errorMessage: string): ExecutionErrorType { return 'generic' } +function moduleNotFoundHelpMessage(): string { + return ( + "Note: Only Pyodide's built-in packages are available in the browser. " + + 'Packages installed via pip cannot be used here. ' + + 'See https://pyodide.org/en/stable/usage/packages-in-pyodide.html ' + + 'for the list of supported packages.' + ) +} + function ensureDirectoryExists(fs: PyodideFS, filePath: string) { const directory = path.dirname(filePath) if (directory === '.' || directory === '/') { @@ -221,7 +230,12 @@ async function handleRunCode(msg: RunCodeRequest) { if (runError) { const errorMessage = runError instanceof Error ? runError.message : String(runError) - postFailure('stderr', errorMessage, classifyErrorType(errorMessage)) + const errorType = classifyErrorType(errorMessage) + const fullMessage = + errorType === 'ModuleNotFoundError' + ? `${errorMessage}\n${moduleNotFoundHelpMessage()}` + : errorMessage + postFailure('stderr', fullMessage, errorType) return } diff --git a/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx b/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx index 1e5d8d12df..87dbea0fe0 100644 --- a/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx +++ b/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx @@ -257,6 +257,9 @@ describe('', function () { 'have.class', 'ide-redesign-python-output-pane-line-stderr' ) + cy.findByText("Only Pyodide's built-in packages", { exact: false }).should( + 'not.exist' + ) }) it('renders the interrupt message as an info line', function () { @@ -380,4 +383,43 @@ describe('', function () { ) cy.findByText('hello from tomli').should('exist') }) + + it('augments ModuleNotFoundError output with help text about supported Pyodide packages', function () { + const executablePythonFileContents = + 'import this_module_does_not_exist_in_pyodide\n' + const projectFiles = { + [pythonExecutableScript.filename]: executablePythonFileContents, + } + const ProjectProvider = makeProjectProvider(projectFiles) + + cy.mount( + executablePythonFileContents, + }, + currentDocumentId: pythonExecutableScript.file_id, + openDocName: pythonExecutableScript.filename, + }, + }} + providers={{ FileTreePathProvider, ProjectProvider }} + > + + + + + ) + + cy.findByRole('button', { name: 'Run Python code' }) + .should('not.be.disabled') + .click() + + cy.findByText('ModuleNotFoundError', { exact: false }).should('exist') + cy.findByText("Only Pyodide's built-in packages", { exact: false }).should( + 'have.class', + 'ide-redesign-python-output-pane-line-stderr' + ) + }) })