From e38f4e18e4005bec64519b292285273cb493f5f2 Mon Sep 17 00:00:00 2001
From: Chris Dryden <117657238+chrisdrydenaltmetric@users.noreply.github.com>
Date: Wed, 3 Jun 2026 12:04:08 +0100
Subject: [PATCH] Merge pull request #33868 from
overleaf/dk-package-loading-tests
[web] Add tests for pyodide worker streams and output pane rendering
GitOrigin-RevId: 41ffc25230be23d68d50c61980cfaf1260a0247d
---
.../components/python-output-pane.spec.tsx | 56 +++++++++++
.../unit/editor/pyodide-worker-client.spec.ts | 97 +++++++++++++++++++
2 files changed, 153 insertions(+)
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 87dbea0fe0..fcf69cbb85 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
@@ -302,6 +302,62 @@ describe('', function () {
)
})
+ it('renders stdout, stderr, and info lines with visually distinct CSS classes', function () {
+ const executablePythonFileContents = [
+ 'import sys',
+ "print('hello!')",
+ "sys.stderr.write('boom\\n')",
+ 'while True:',
+ ' pass',
+ ].join('\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('hello!')
+ .should('have.class', 'ide-redesign-python-output-pane-line-stdout')
+ .and('not.have.class', 'ide-redesign-python-output-pane-line-stderr')
+ .and('not.have.class', 'ide-redesign-python-output-pane-line-info')
+ cy.findByText('boom')
+ .should('have.class', 'ide-redesign-python-output-pane-line-stderr')
+ .and('not.have.class', 'ide-redesign-python-output-pane-line-stdout')
+ .and('not.have.class', 'ide-redesign-python-output-pane-line-info')
+
+ cy.findByRole('button', { name: 'Stop Python execution' })
+ .should('not.be.disabled')
+ .click()
+
+ cy.findByText('Execution interrupted')
+ .should('have.class', 'ide-redesign-python-output-pane-line-info')
+ .and('not.have.class', 'ide-redesign-python-output-pane-line-stdout')
+ .and('not.have.class', 'ide-redesign-python-output-pane-line-stderr')
+ })
+
it('can load common python data analysis packages on code execution', function () {
const executablePythonFileContents = [
'import tomli',
diff --git a/services/web/test/frontend/features/ide-react/unit/editor/pyodide-worker-client.spec.ts b/services/web/test/frontend/features/ide-react/unit/editor/pyodide-worker-client.spec.ts
index 3d082d62ab..81169136af 100644
--- a/services/web/test/frontend/features/ide-react/unit/editor/pyodide-worker-client.spec.ts
+++ b/services/web/test/frontend/features/ide-react/unit/editor/pyodide-worker-client.spec.ts
@@ -587,6 +587,103 @@ describe('PyodideWorkerClient', function () {
})
})
+ describe('output-line forwarding', function () {
+ function setupClientWithOutputTracking() {
+ const outputCalls: Parameters[] = []
+ const client = new PyodideWorkerClient({
+ baseAssetPath: BASE_ASSET_PATH,
+ createWorker,
+ onOutput: (...args) => {
+ outputCalls.push(args)
+ },
+ fileUploader: fileUploaderStub,
+ })
+ const worker = WorkerMock.instances[0]
+ worker.emitMessage({ type: 'listening' })
+ return { client, worker, outputCalls }
+ }
+
+ it('forwards stdout output-line events to the output callback', function () {
+ const { worker, outputCalls } = setupClientWithOutputTracking()
+
+ worker.emitMessage({
+ type: 'output-line',
+ stream: 'stdout',
+ line: 'hello!',
+ fileId: 'main.py',
+ executionId: 'exec-stdout',
+ })
+
+ expect(outputCalls).to.deep.equal([
+ ['stdout', 'hello!', 'main.py', 'exec-stdout'],
+ ])
+ })
+
+ it('forwards stderr output-line events to the output callback', function () {
+ const { worker, outputCalls } = setupClientWithOutputTracking()
+
+ worker.emitMessage({
+ type: 'output-line',
+ stream: 'stderr',
+ line: 'boom',
+ fileId: 'main.py',
+ executionId: 'exec-stderr',
+ })
+
+ expect(outputCalls).to.deep.equal([
+ ['stderr', 'boom', 'main.py', 'exec-stderr'],
+ ])
+ })
+
+ it('forwards info output-line events to the output callback', function () {
+ const { worker, outputCalls } = setupClientWithOutputTracking()
+
+ worker.emitMessage({
+ type: 'output-line',
+ stream: 'info',
+ line: 'Loading numpy from package index',
+ fileId: 'main.py',
+ executionId: 'exec-info',
+ })
+
+ expect(outputCalls).to.deep.equal([
+ ['info', 'Loading numpy from package index', 'main.py', 'exec-info'],
+ ])
+ })
+
+ it('preserves stream type when forwarding stdout, stderr, and info in sequence', function () {
+ const { worker, outputCalls } = setupClientWithOutputTracking()
+
+ worker.emitMessage({
+ type: 'output-line',
+ stream: 'info',
+ line: 'Loading package',
+ fileId: 'main.py',
+ executionId: 'exec-mixed',
+ })
+ worker.emitMessage({
+ type: 'output-line',
+ stream: 'stdout',
+ line: 'result',
+ fileId: 'main.py',
+ executionId: 'exec-mixed',
+ })
+ worker.emitMessage({
+ type: 'output-line',
+ stream: 'stderr',
+ line: 'warning',
+ fileId: 'main.py',
+ executionId: 'exec-mixed',
+ })
+
+ expect(outputCalls.map(call => call[0])).to.deep.equal([
+ 'info',
+ 'stdout',
+ 'stderr',
+ ])
+ })
+ })
+
describe('reset', function () {
it('terminates the current worker and creates a new one', function () {
const client = new PyodideWorkerClient({