chore: make dev server only use public config (#32441)

In preparation to make it a part of a plugin.
This commit is contained in:
Dmitry Gozman 2024-09-04 01:29:55 -07:00 committed by GitHub
parent d7393f998e
commit 60631409d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 85 additions and 27 deletions

View File

@ -21,6 +21,7 @@ import { resolveDirs } from './viteUtils';
import { runDevServer } from './devServer';
import type { FullConfigInternal } from 'playwright/lib/common/config';
import { removeFolderAndLogToConsole } from 'playwright/lib/runner/testServer';
import type { FullConfig } from 'playwright/types/test';
export async function clearCacheCommand(config: FullConfigInternal) {
const dirs = await resolveDirs(config.configDir, config.config);
@ -34,6 +35,6 @@ export async function findRelatedTestFilesCommand(files: string[], config: Full
return { testFiles: affectedTestFiles(files) };
}
export async function runDevServerCommand(config: FullConfigInternal) {
export async function runDevServerCommand(config: FullConfig) {
return await runDevServer(config);
}

View File

@ -17,28 +17,26 @@
import fs from 'fs';
import path from 'path';
import { Watcher } from 'playwright/lib/fsWatcher';
import { Runner } from 'playwright/lib/runner/runner';
import type { PluginContext } from 'rollup';
import { source as injectedSource } from './generated/indexSource';
import { createConfig, populateComponentsFromTests, resolveDirs, transformIndexFile, frameworkConfig } from './viteUtils';
import type { ComponentRegistry } from './viteUtils';
import type { FullConfigInternal } from 'playwright/lib/common/config';
import type { FullConfig } from 'playwright/test';
export async function runDevServer(config: FullConfigInternal): Promise<() => Promise<void>> {
const { registerSourceFile, frameworkPluginFactory } = frameworkConfig(config.config);
const runner = new Runner(config);
await runner.loadAllTests();
export async function runDevServer(config: FullConfig): Promise<() => Promise<void>> {
const { registerSourceFile, frameworkPluginFactory } = frameworkConfig(config);
const componentRegistry: ComponentRegistry = new Map();
await populateComponentsFromTests(componentRegistry);
const dirs = await resolveDirs(config.configDir, config.config);
const configDir = config.configFile ? path.dirname(config.configFile) : config.rootDir;
const dirs = await resolveDirs(configDir, config);
if (!dirs) {
// eslint-disable-next-line no-console
console.log(`Template file playwright/index.html is missing.`);
return async () => {};
}
const registerSource = injectedSource + '\n' + await fs.promises.readFile(registerSourceFile, 'utf-8');
const viteConfig = await createConfig(dirs, config.config, frameworkPluginFactory, false);
const viteConfig = await createConfig(dirs, config, frameworkPluginFactory, false);
viteConfig.plugins.push({
name: 'playwright:component-index',
@ -57,8 +55,8 @@ export async function runDevServer(config: FullConfigInternal): Promise<() => Pr
const projectDirs = new Set<string>();
const projectOutputs = new Set<string>();
for (const p of config.projects) {
projectDirs.add(p.project.testDir);
projectOutputs.add(p.project.outputDir);
projectDirs.add(p.testDir);
projectOutputs.add(p.outputDir);
}
const globalWatcher = new Watcher(async () => {

View File

@ -95,13 +95,14 @@ function addDevServerCommand(program: Command) {
command.description('start dev server');
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
command.action(async options => {
const configInternal = await loadConfigFromFileRestartIfNeeded(options.config);
if (!configInternal)
const config = await loadConfigFromFileRestartIfNeeded(options.config);
if (!config)
return;
const { config } = configInternal;
const implementation = (config as any)['@playwright/test']?.['cli']?.['dev-server'];
const implementation = (config.config as any)['@playwright/test']?.['cli']?.['dev-server'];
if (implementation) {
await implementation(configInternal);
const runner = new Runner(config);
await runner.loadAllTests();
await implementation(config.config);
} else {
console.log(`DevServer is not available in the package you are using. Did you mean to use component testing?`);
gracefullyProcessExitDoNotHang(1);

View File

@ -176,17 +176,16 @@ export class TestServerDispatcher implements TestServerInterface {
async startDevServer(params: Parameters<TestServerInterface['startDevServer']>[0]): ReturnType<TestServerInterface['startDevServer']> {
if (this._devServerHandle)
return { status: 'failed', report: [] };
const { reporter, report } = await this._collectingInternalReporter();
const config = await this._loadConfigOrReportError(reporter);
const { config, report, reporter, status } = await this._innerListTests({});
if (!config)
return { status: 'failed', report };
return { status, report };
const devServerCommand = (config.config as any)['@playwright/test']?.['cli']?.['dev-server'];
if (!devServerCommand) {
reporter.onError({ message: 'No dev-server command found in the configuration' });
return { status: 'failed', report };
}
try {
this._devServerHandle = await devServerCommand(config);
this._devServerHandle = await devServerCommand(config.config);
return { status: 'passed', report };
} catch (e) {
reporter.onError(serializeError(e));
@ -237,13 +236,21 @@ export class TestServerDispatcher implements TestServerInterface {
async listTests(params: Parameters<TestServerInterface['listTests']>[0]): ReturnType<TestServerInterface['listTests']> {
let result: Awaited<ReturnType<TestServerInterface['listTests']>>;
this._queue = this._queue.then(async () => {
result = await this._innerListTests(params);
const { config, report, status } = await this._innerListTests(params);
if (config)
await this._updateWatchedDirs(config);
result = { report, status };
}).catch(printInternalError);
await this._queue;
return result!;
}
private async _innerListTests(params: Parameters<TestServerInterface['listTests']>[0]): ReturnType<TestServerInterface['listTests']> {
private async _innerListTests(params: Parameters<TestServerInterface['listTests']>[0]): Promise<{
report: ReportEntry[],
reporter: InternalReporter,
status: reporterTypes.FullResult['status'],
config?: FullConfigInternal,
}> {
const overrides: ConfigCLIOverrides = {
repeatEach: 1,
retries: 0,
@ -252,7 +259,7 @@ export class TestServerDispatcher implements TestServerInterface {
const { reporter, report } = await this._collectingInternalReporter();
const config = await this._loadConfigOrReportError(reporter, overrides);
if (!config)
return { report, status: 'failed' };
return { report, reporter, status: 'failed' };
config.cliArgs = params.locations || [];
config.cliGrep = params.grep;
@ -266,7 +273,10 @@ export class TestServerDispatcher implements TestServerInterface {
const status = await taskRunner.run(testRun, 0);
await reporter.onEnd({ status });
await reporter.onExit();
return { config, report, reporter, status };
}
private async _updateWatchedDirs(config: FullConfigInternal) {
this._watchedProjectDirs = new Set();
this._ignoredProjectOutputs = new Set();
for (const p of config.projects) {
@ -281,11 +291,10 @@ export class TestServerDispatcher implements TestServerInterface {
}
if (this._watchTestDirs)
await this.updateWatcher(false);
return { report, status };
await this._updateWatcher(false);
}
private async updateWatcher(reportPending: boolean) {
private async _updateWatcher(reportPending: boolean) {
await this._watcher.update([...this._watchedProjectDirs, ...this._watchedTestDependencies], [...this._ignoredProjectOutputs], reportPending);
}
@ -358,7 +367,7 @@ export class TestServerDispatcher implements TestServerInterface {
this._watchedTestDependencies.add(fileName);
dependenciesForTestFile(fileName).forEach(file => this._watchedTestDependencies.add(file));
}
await this.updateWatcher(true);
await this._updateWatcher(true);
}
async findRelatedTestFiles(params: Parameters<TestServerInterface['findRelatedTestFiles']>[0]): ReturnType<TestServerInterface['findRelatedTestFiles']> {

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { test, expect, playwrightCtConfigText } from './playwright-test-fixtures';
test.describe.configure({ mode: 'parallel' });
test('should run dev-server and use it for tests', async ({ writeFiles, runInlineTest, startCLICommand }) => {
await writeFiles({
'playwright.config.ts': playwrightCtConfigText,
'playwright/index.html': `<script type="module" src="./index.ts"></script>`,
'playwright/index.ts': ``,
'src/button.tsx': `
export const Button = () => <button>Button</button>;
`,
'src/button.test.tsx': `
import { test, expect } from '@playwright/experimental-ct-react';
import { Button } from './button';
test('pass', async ({ mount }) => {
const component = await mount(<Button></Button>);
await expect(component).toHaveText('Button', { timeout: 1 });
});
`,
});
const devServerProcess = await startCLICommand({}, 'dev-server');
await devServerProcess.waitForOutput('Dev Server listening on');
const result = await runInlineTest({}, { workers: 1 });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
expect(result.output).toContain('Dev Server is already running at');
await devServerProcess.kill();
});