From 7758b330b1cc3ec08f39d4e89e0f58c3cbdbfa4f Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 22 Aug 2024 05:48:31 -0700 Subject: [PATCH] fix(ui mode): make sure that reload does correctly restart the webserver (#32263) Fixes #32103. --- .../playwright/src/plugins/webServerPlugin.ts | 2 + packages/playwright/src/runner/testServer.ts | 20 ++++----- .../ui-mode-test-setup.spec.ts | 42 ++++++++++++++++++- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/packages/playwright/src/plugins/webServerPlugin.ts b/packages/playwright/src/plugins/webServerPlugin.ts index 86f84ad652..96a369bddb 100644 --- a/packages/playwright/src/plugins/webServerPlugin.ts +++ b/packages/playwright/src/plugins/webServerPlugin.ts @@ -73,7 +73,9 @@ export class WebServerPlugin implements TestRunnerPlugin { } public async teardown() { + debugWebServer(`Terminating the WebServer`); await this._killProcess?.(); + debugWebServer(`Terminated the WebServer`); } private async _startProcess(): Promise { diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index 5a498337af..7ed1d18191 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -114,14 +114,11 @@ class TestServerDispatcher implements TestServerInterface { } async initialize(params: Parameters[0]): ReturnType { - if (params.serializer) - this._serializer = params.serializer; - if (params.closeOnDisconnect) - this._closeOnDisconnect = true; - if (params.interceptStdio) - await this._setInterceptStdio(true); - if (params.watchTestDirs) - this._watchTestDirs = true; + // Note: this method can be called multiple times, for example from a new connection after UI mode reload. + this._serializer = params.serializer || require.resolve('./uiModeReporter'); + this._closeOnDisconnect = !!params.closeOnDisconnect; + await this._setInterceptStdio(!!params.interceptStdio); + this._watchTestDirs = !!params.watchTestDirs; } async ping() {} @@ -161,7 +158,6 @@ class TestServerDispatcher implements TestServerInterface { return { status: 'failed', report }; } - webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p })); const { collectingReporter, report } = await this._collectingReporter(); const listReporter = new ListReporter(); const taskRunner = createTaskRunnerForWatchSetup(config, [collectingReporter, listReporter]); @@ -418,10 +414,12 @@ class TestServerDispatcher implements TestServerInterface { try { const config = await loadConfig(this._configLocation, overrides); // Preserve plugin instances between setup and build. - if (!this._plugins) + if (!this._plugins) { + webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p })); this._plugins = config.plugins || []; - else + } else { config.plugins.splice(0, config.plugins.length, ...this._plugins); + } return { config }; } catch (e) { return { config: null, error: serializeError(e) }; diff --git a/tests/playwright-test/ui-mode-test-setup.spec.ts b/tests/playwright-test/ui-mode-test-setup.spec.ts index 08857d5508..e8809ddad9 100644 --- a/tests/playwright-test/ui-mode-test-setup.spec.ts +++ b/tests/playwright-test/ui-mode-test-setup.spec.ts @@ -15,6 +15,7 @@ */ import { test, expect, retries, dumpTestTree } from './ui-mode-fixtures'; +import path from 'path'; test.describe.configure({ mode: 'parallel', retries }); @@ -245,4 +246,43 @@ for (const useWeb of [true, false]) { ]); }); }); -} \ No newline at end of file +} + +test('should restart webserver on reload', async ({ runUITest }) => { + const SIMPLE_SERVER_PATH = path.join(__dirname, 'assets', 'simple-server.js'); + const port = test.info().workerIndex * 2 + 10500; + + const { page } = await runUITest({ + 'playwright.config.ts': ` + import { defineConfig } from '@playwright/test'; + export default defineConfig({ + webServer: { + command: 'node ${JSON.stringify(SIMPLE_SERVER_PATH)} ${port}', + port: ${port}, + reuseExistingServer: false, + }, + }); + `, + 'a.test.js': ` + import { test, expect } from '@playwright/test'; + test('should work', async ({ page }) => { + await page.goto('http://localhost:${port}'); + }); + ` + }, { DEBUG: 'pw:webserver' }); + await page.getByTitle('Run all').click(); + await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); + + await page.getByTitle('Toggle output').click(); + await expect(page.getByTestId('output')).toContainText('[WebServer] listening'); + + await page.getByTitle('Clear output').click(); + await expect(page.getByTestId('output')).not.toContainText('[WebServer] listening'); + + await page.getByTitle('Reload').click(); + await expect(page.getByTestId('output')).toContainText('[WebServer] listening'); + await expect(page.getByTestId('output')).not.toContainText('set reuseExistingServer:true'); + + await page.getByTitle('Run all').click(); + await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)'); +});