Back to Repositories

Testing Cancel Event Handling and Iterative Functions in gradio-app

This test suite validates cancel event handling and iterative function behavior in a UI context, focusing on state updates, cancellation, and cleanup. The tests ensure proper interaction between the frontend and Python backend while maintaining UI responsiveness.

Test Coverage Overview

The test suite covers three critical aspects of cancel events and iterative functions:
  • Progressive UI updates during iteration
  • Cancellation functionality mid-execution
  • Cleanup behavior when closing the page
Integration points include frontend-backend communication, DOM updates, and file system interactions for logging.

Implementation Analysis

The testing approach utilizes Playwright’s page object model for UI interaction and assertions. The implementation employs async/await patterns with interval-based polling for state changes, explicit timeouts for race condition prevention, and file system checks for backend state verification.

Technical patterns include button click simulations, input value monitoring, and cleanup verification through log file analysis.

Technical Details

  • Testing Framework: Custom framework with Playwright integration
  • File System Operations: Node’s fs module for log verification
  • Async Utilities: setTimeout and setInterval for timing control
  • Selectors: Label and text-based element location
  • Assertion Methods: expect() with various matchers

Best Practices Demonstrated

The test suite exemplifies robust testing practices through comprehensive state verification and cleanup handling.

  • Explicit waiting strategies for async operations
  • Proper cleanup of intervals and resources
  • Multiple assertion points for thorough validation
  • Integration testing of UI and backend components
  • Verification of both positive and negative cases

gradio-app/gradio

js/spa/test/cancel_events.spec.ts

            
import { test, expect } from "@self/tootils";
import { readFileSync } from "fs";

test("when using an iterative function the UI should update over time as iteration results are received", async ({
	page
}) => {
	const start_button = await page.locator("button", {
		hasText: /Start Iterating/
	});

	let output_values: string[] = [];
	let last_output_value = "";
	let interval = setInterval(async () => {
		let value = await page.getByLabel("Iterative Output").inputValue();
		if (value !== last_output_value) {
			output_values.push(value);
			last_output_value = value;
		}
	}, 100);

	await start_button.click();
	await expect(page.getByLabel("Iterative Output")).toHaveValue("8");
	clearInterval(interval);
	for (let i = 1; i < 8; i++) {
		expect(output_values).toContain(i.toString());
	}
});

test("when using an iterative function it should be possible to cancel the function, after which the UI should stop updating", async ({
	page
}) => {
	const start_button = await page.locator("button", {
		hasText: /Start Iterating/
	});
	const stop_button = await page.locator("button", {
		hasText: /Stop Iterating/
	});
	const textbox = await page.getByLabel("Iterative Output");

	await start_button.click();
	await expect(textbox).toHaveValue("0");
	await stop_button.click();
	await expect(textbox).toHaveValue("0");
	await page.waitForTimeout(1000);
	await expect(textbox).toHaveValue("0");
});

test("when using an iterative function and the user closes the page, the python function should stop running", async ({
	page
}) => {
	const start_button = await page.locator("button", {
		hasText: /Start Iterating/
	});

	await start_button.click();
	await page.waitForTimeout(300);
	await page.close();

	// wait for the duration of the entire iteration
	// check that the final value did not get written
	// to the log file. That's our proof python stopped
	// running
	await new Promise((resolve) => setTimeout(resolve, 2000));
	const data = readFileSync(
		"../../demo/cancel_events/cancel_events_output_log.txt",
		"utf-8"
	);
	expect(data).toContain("Current step: 0");
	expect(data).not.toContain("Current step: 8");
});