Back to Repositories

Testing ChatInterface Hot-Reload Implementation in gradio-app/gradio

This test suite validates the hot-reload functionality of Gradio’s ChatInterface component in development mode. It ensures that stateful chat applications can properly reload and maintain functionality when source code changes are made during runtime.

Test Coverage Overview

The test suite provides comprehensive coverage of ChatInterface’s hot-reload capabilities.

Key areas tested include:
  • Dynamic code updates during runtime
  • State preservation during reloads
  • Chat message handling after reload
  • UI component updates and visibility
  • Response formatting and display

Implementation Analysis

The test implementation uses Playwright for browser automation and interaction simulation. It employs a robust setup/teardown pattern with beforeAll/afterAll hooks to manage test environment state.

Notable patterns include:
  • Dynamic file manipulation for testing hot-reload scenarios
  • Process management for Gradio server instances
  • Async/await patterns for UI interactions
  • Explicit timeout handling for reload operations

Technical Details

Testing tools and configuration:
  • Playwright test runner and assertion library
  • Node’s child_process for file and process management
  • Custom utility functions for app lifecycle management
  • Environment variable configuration for Python process control
  • Port management for local development server

Best Practices Demonstrated

The test suite exemplifies several testing best practices for complex UI applications.

Notable practices include:
  • Proper resource cleanup in teardown phases
  • Isolation of test environments
  • Explicit waiting strategies for async operations
  • Error handling with try/finally blocks
  • Clear separation of setup, execution, and verification phases

gradio-app/gradio

js/spa/test/test_chatinterface.reload.spec.ts

            
import { test, expect } from "@playwright/test";
import { spawnSync } from "node:child_process";
import { launch_app_background, kill_process } from "./utils";
import { join } from "path";

const demo_file = "chat_demo.py";
let _process;

test.beforeAll(() => {
	const demo = `
import gradio as gr
		
def greet(msg, history):
	return "Hello"

demo = gr.ChatInterface(fn=greet)

if __name__ == "__main__":
	demo.launch()
	`;
	// write contents of demo to a local 'run.py' file
	spawnSync(`echo '${demo}' > ${join(process.cwd(), demo_file)}`, {
		shell: true,
		stdio: "pipe",
		env: {
			...process.env,
			PYTHONUNBUFFERED: "true"
		}
	});
});

test.afterAll(() => {
	if (_process) kill_process(_process);
	spawnSync(`rm  ${join(process.cwd(), demo_file)}`, {
		shell: true,
		stdio: "pipe",
		env: {
			...process.env,
			PYTHONUNBUFFERED: "true"
		}
	});
});

test("gradio dev mode correctly reloads a stateful ChatInterface demo", async ({
	page
}) => {
	test.setTimeout(20 * 1000);

	try {
		const { _process: server_process, port: port } =
			await launch_app_background(
				`gradio ${join(process.cwd(), demo_file)}`,
				process.cwd()
			);
		_process = server_process;
		console.log("Connected to port", port);
		const demo = `
import gradio as gr

def greet(msg, history):
	return f"You typed: {msg}"

demo = gr.ChatInterface(fn=greet, textbox=gr.Textbox(label="foo", placeholder="Type a message..."))

if __name__ == "__main__":
	demo.launch()
		`;
		// write contents of demo to a local 'run.py' file
		await page.goto(`http://localhost:${port}`);
		await page.waitForTimeout(2000);
		spawnSync(`echo '${demo}' > ${join(process.cwd(), demo_file)}`, {
			shell: true,
			stdio: "pipe",
			env: {
				...process.env,
				PYTHONUNBUFFERED: "true"
			}
		});

		await expect(page.getByLabel("foo")).toBeVisible();
		const textbox = page.getByTestId("textbox").first();

		await textbox.fill("hello");
		await textbox.press("Enter");

		await expect(textbox).toHaveValue("");
		const response = page.locator(".bot  p", {
			hasText: "You typed: hello"
		});
		await expect(response).toBeVisible();
	} finally {
		if (_process) kill_process(_process);
	}
});