From e2f3472f6a024add45d7837a26a988194e5d66d8 Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Wed, 13 Mar 2024 19:48:11 -0400 Subject: [PATCH] Add reload mode e2e test --- .config/playwright-setup.js | 3 +- .config/playwright.config.js | 2 +- .github/workflows/test-functional.yml | 4 ++ js/app/test/hello_world.reload.spec.ts | 81 ++++++++++++++++++++++++++ js/app/test/utils.ts | 61 +++++++++++++++++++ 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 js/app/test/hello_world.reload.spec.ts create mode 100644 js/app/test/utils.ts diff --git a/.config/playwright-setup.js b/.config/playwright-setup.js index 3fc61854f8a23..f8ebf18378f93 100644 --- a/.config/playwright-setup.js +++ b/.config/playwright-setup.js @@ -16,7 +16,8 @@ const test_files = readdirSync(TEST_FILES_PATH) (f) => f.endsWith("spec.ts") && !f.endsWith(".skip.spec.ts") && - !f.endsWith(".component.spec.ts") + !f.endsWith(".component.spec.ts") && + !f.endsWith(".reload.spec.ts") ) .map((f) => basename(f, ".spec.ts")); diff --git a/.config/playwright.config.js b/.config/playwright.config.js index 48b0b4b77114e..e4d6899156eee 100644 --- a/.config/playwright.config.js +++ b/.config/playwright.config.js @@ -23,7 +23,7 @@ const base = defineConfig({ }); const normal = defineConfig(base, { - globalSetup: "./playwright-setup.js" + globalSetup: process.env.CUSTOM_TEST ? undefined : "./playwright-setup.js" }); normal.projects = undefined; // Explicitly unset this field due to https://github.com/microsoft/playwright/issues/28795 diff --git a/.github/workflows/test-functional.yml b/.github/workflows/test-functional.yml index d7e6b415c3570..7004cbc56b194 100644 --- a/.github/workflows/test-functional.yml +++ b/.github/workflows/test-functional.yml @@ -74,6 +74,10 @@ jobs: run: | . venv/bin/activate pnpm run test:ct + - name: run reload mode test + run: | + . venv/bin/activate + CUSTOM_TEST=1 pnpm --filter @gradio/app test:browser:reload - name: Run Lite E2E tests run: | . venv/bin/activate diff --git a/js/app/test/hello_world.reload.spec.ts b/js/app/test/hello_world.reload.spec.ts new file mode 100644 index 0000000000000..712ac26bb0a07 --- /dev/null +++ b/js/app/test/hello_world.reload.spec.ts @@ -0,0 +1,81 @@ +import { test, expect } from "@playwright/test"; +import { spawnSync } from "node:child_process"; +import { launch_app_background, kill_process } from "./utils"; +import { join } from "path"; + +test.beforeAll(() => { + const demo = ` +import gradio as gr + +def greet(name): + return "Hello " + name + "!" + +demo = gr.Interface(fn=greet, inputs="text", outputs="text") + +if __name__ == "__main__": + demo.launch() +`; + // write contents of demo to a local 'run.py' file + spawnSync(`echo '${demo}' > ${join(process.cwd(), "run.py")}`, { + shell: true, + stdio: "pipe", + env: { + ...process.env, + PYTHONUNBUFFERED: "true" + } + }); +}); + +test.afterAll(() => { + spawnSync(`rm ${join(process.cwd(), "run.py")}`, { + shell: true, + stdio: "pipe", + env: { + ...process.env, + PYTHONUNBUFFERED: "true" + } + }); +}); + +test("gradio dev mode correctly reloads the page", async ({ page }) => { + test.setTimeout(20 * 1000); + + let _process; + try { + const port = 7880; + const { process: _process } = await launch_app_background( + `GRADIO_SERVER_PORT=${port} gradio ${join(process.cwd(), "run.py")}`, + process.cwd() + ); + console.log("Connected to port", port); + const demo = ` +import gradio as gr + +def greet(name): + return "Hello " + name + "!" + +dmeo = gr.Interface(fn=greet, inputs=gr.Textbox(label="x"), outputs=gr.Textbox(label="foo")) + +if __name__ == "__main__": + demo.launch() + `; + // write contents of demo to a local 'run.py' file + spawnSync(`echo '${demo}' > ${join(process.cwd(), "run.py")}`, { + shell: true, + stdio: "pipe", + env: { + ...process.env, + PYTHONUNBUFFERED: "true" + } + }); + + await page.goto(`http://localhost:${port}`); + + await page.getByLabel("x").fill("Maria"); + await page.getByRole("button", { name: "Submit" }).click(); + + await expect(page.getByLabel("foo")).toHaveValue("Hello Maria!"); + } finally { + if (_process) kill_process(_process); + } +}); diff --git a/js/app/test/utils.ts b/js/app/test/utils.ts new file mode 100644 index 0000000000000..2ba1bbcbb1799 --- /dev/null +++ b/js/app/test/utils.ts @@ -0,0 +1,61 @@ +import { spawn } from "node:child_process"; + +import type { ChildProcess } from "child_process"; + +export function kill_process(process: ChildProcess) { + process.kill("SIGKILL"); +} + +type LaunchAppBackgroundReturn = { + port: number; + process: ChildProcess; +}; + +export const launch_app_background = async ( + command: string, + cwd?: string +): Promise => { + const _process = spawn(command, { + shell: true, + stdio: "pipe", + cwd: cwd || process.cwd(), + env: { + ...process.env, + PYTHONUNBUFFERED: "true" + } + }); + + _process.stdout.setEncoding("utf8"); + _process.stderr.setEncoding("utf8"); + + _process.on("exit", () => kill_process(_process)); + _process.on("close", () => kill_process(_process)); + _process.on("disconnect", () => kill_process(_process)); + + let port; + + function std_out(data: any) { + const _data: string = data.toString(); + console.log(_data); + + const portRegExp = /:(\d+)/; + const match = portRegExp.exec(_data); + + if (match && match[1] && _data.includes("Running on local URL:")) { + port = parseInt(match[1], 10); + } + } + + function std_err(data: any) { + const _data: string = data.toString(); + console.log(_data); + } + + _process.stdout.on("data", std_out); + _process.stderr.on("data", std_err); + + while (!port) { + await new Promise((r) => setTimeout(r, 1000)); + } + return { port: port, process: _process }; +};