chore: move dev server to config-based framework extensibility (#30234)
This commit is contained in:
parent
e18a358bc6
commit
5043bd55dc
3
package-lock.json
generated
3
package-lock.json
generated
|
|
@ -8220,9 +8220,6 @@
|
||||||
"playwright-core": "1.44.0-next",
|
"playwright-core": "1.44.0-next",
|
||||||
"vite": "^5.0.13"
|
"vite": "^5.0.13"
|
||||||
},
|
},
|
||||||
"bin": {
|
|
||||||
"playwright": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { program } = require('./lib/program');
|
|
||||||
program.parse(process.argv);
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
const { test: baseTest, expect, devices, defineConfig: originalDefineConfig } = require('playwright/test');
|
const { test: baseTest, expect, devices, defineConfig: originalDefineConfig } = require('playwright/test');
|
||||||
const { fixtures } = require('./lib/mount');
|
const { fixtures } = require('./lib/mount');
|
||||||
const { clearCacheCommand, findRelatedTestFilesCommand } = require('./lib/cliOverrides');
|
const { clearCacheCommand, runDevServerCommand, findRelatedTestFilesCommand } = require('./lib/cliOverrides');
|
||||||
const { createPlugin } = require('./lib/vitePlugin');
|
const { createPlugin } = require('./lib/vitePlugin');
|
||||||
|
|
||||||
const defineConfig = (...configs) => {
|
const defineConfig = (...configs) => {
|
||||||
|
|
@ -31,6 +31,7 @@ const defineConfig = (...configs) => {
|
||||||
],
|
],
|
||||||
cli: {
|
cli: {
|
||||||
'clear-cache': clearCacheCommand,
|
'clear-cache': clearCacheCommand,
|
||||||
|
'dev-server': runDevServerCommand,
|
||||||
'find-related-test-files': findRelatedTestFilesCommand,
|
'find-related-test-files': findRelatedTestFilesCommand,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,5 @@
|
||||||
"playwright-core": "1.44.0-next",
|
"playwright-core": "1.44.0-next",
|
||||||
"vite": "^5.0.13",
|
"vite": "^5.0.13",
|
||||||
"playwright": "1.44.0-next"
|
"playwright": "1.44.0-next"
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"playwright": "cli.js"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import { affectedTestFiles, cacheDir } from 'playwright/lib/transform/compilatio
|
||||||
import { buildBundle } from './vitePlugin';
|
import { buildBundle } from './vitePlugin';
|
||||||
import { resolveDirs } from './viteUtils';
|
import { resolveDirs } from './viteUtils';
|
||||||
import type { FullConfig, Suite } from 'playwright/types/testReporter';
|
import type { FullConfig, Suite } from 'playwright/types/testReporter';
|
||||||
|
import { runDevServer } from './devServer';
|
||||||
|
import type { FullConfigInternal } from 'playwright/lib/common/config';
|
||||||
|
|
||||||
export async function clearCacheCommand(config: FullConfig, configDir: string) {
|
export async function clearCacheCommand(config: FullConfig, configDir: string) {
|
||||||
const dirs = await resolveDirs(configDir, config);
|
const dirs = await resolveDirs(configDir, config);
|
||||||
|
|
@ -32,3 +34,7 @@ export async function findRelatedTestFilesCommand(files: string[], config: Full
|
||||||
await buildBundle(config, configDir, suite);
|
await buildBundle(config, configDir, suite);
|
||||||
return { testFiles: affectedTestFiles(files) };
|
return { testFiles: affectedTestFiles(files) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function runDevServerCommand(config: FullConfigInternal) {
|
||||||
|
return await runDevServer(config);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,14 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Watcher } from 'playwright/lib/fsWatcher';
|
import { Watcher } from 'playwright/lib/fsWatcher';
|
||||||
import { loadConfigFromFileRestartIfNeeded } from 'playwright/lib/common/configLoader';
|
|
||||||
import { Runner } from 'playwright/lib/runner/runner';
|
import { Runner } from 'playwright/lib/runner/runner';
|
||||||
import type { PluginContext } from 'rollup';
|
import type { PluginContext } from 'rollup';
|
||||||
import { source as injectedSource } from './generated/indexSource';
|
import { source as injectedSource } from './generated/indexSource';
|
||||||
import { createConfig, populateComponentsFromTests, resolveDirs, transformIndexFile, frameworkConfig } from './viteUtils';
|
import { createConfig, populateComponentsFromTests, resolveDirs, transformIndexFile, frameworkConfig } from './viteUtils';
|
||||||
import type { ComponentRegistry } from './viteUtils';
|
import type { ComponentRegistry } from './viteUtils';
|
||||||
|
import type { FullConfigInternal } from 'playwright/lib/common/config';
|
||||||
|
|
||||||
export async function runDevServer(configFile: string) {
|
export async function runDevServer(config: FullConfigInternal): Promise<() => Promise<void>> {
|
||||||
const config = await loadConfigFromFileRestartIfNeeded(configFile);
|
|
||||||
if (!config)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const { registerSourceFile, frameworkPluginFactory } = frameworkConfig(config.config);
|
const { registerSourceFile, frameworkPluginFactory } = frameworkConfig(config.config);
|
||||||
const runner = new Runner(config);
|
const runner = new Runner(config);
|
||||||
await runner.loadAllTests();
|
await runner.loadAllTests();
|
||||||
|
|
@ -39,7 +35,7 @@ export async function runDevServer(configFile: string) {
|
||||||
if (!dirs) {
|
if (!dirs) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`Template file playwright/index.html is missing.`);
|
console.log(`Template file playwright/index.html is missing.`);
|
||||||
return;
|
return async () => {};
|
||||||
}
|
}
|
||||||
const registerSource = injectedSource + '\n' + await fs.promises.readFile(registerSourceFile, 'utf-8');
|
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.config, frameworkPluginFactory, false);
|
||||||
|
|
@ -56,7 +52,7 @@ export async function runDevServer(configFile: string) {
|
||||||
await devServer.listen();
|
await devServer.listen();
|
||||||
const protocol = viteConfig.server.https ? 'https:' : 'http:';
|
const protocol = viteConfig.server.https ? 'https:' : 'http:';
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`Test Server listening on ${protocol}//${viteConfig.server.host || 'localhost'}:${viteConfig.server.port}`);
|
console.log(`Dev Server listening on ${protocol}//${viteConfig.server.host || 'localhost'}:${viteConfig.server.port}`);
|
||||||
|
|
||||||
const projectDirs = new Set<string>();
|
const projectDirs = new Set<string>();
|
||||||
const projectOutputs = new Set<string>();
|
const projectOutputs = new Set<string>();
|
||||||
|
|
@ -85,4 +81,5 @@ export async function runDevServer(configFile: string) {
|
||||||
devServer.moduleGraph.onFileChange(rootModule.file!);
|
devServer.moduleGraph.onFileChange(rootModule.file!);
|
||||||
});
|
});
|
||||||
globalWatcher.update([...projectDirs], [...projectOutputs], false);
|
globalWatcher.update([...projectDirs], [...projectOutputs], false);
|
||||||
|
return () => Promise.all([devServer.close(), globalWatcher.close()]).then(() => {});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,19 +14,4 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Command } from 'playwright-core/lib/utilsBundle';
|
|
||||||
|
|
||||||
import { program } from 'playwright/lib/program';
|
|
||||||
import { runDevServer } from './devServer';
|
|
||||||
export { program } from 'playwright/lib/program';
|
export { program } from 'playwright/lib/program';
|
||||||
|
|
||||||
function addDevServerCommand(program: Command) {
|
|
||||||
const command = program.command('dev-server', { hidden: true });
|
|
||||||
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(options => {
|
|
||||||
runDevServer(options.config);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addDevServerCommand(program);
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export async function buildBundle(config: FullConfig, configDir: string, suite:
|
||||||
const url = new URL(`${protocol}//${endpoint.host}:${endpoint.port}`);
|
const url = new URL(`${protocol}//${endpoint.host}:${endpoint.port}`);
|
||||||
if (await isURLAvailable(url, true)) {
|
if (await isURLAvailable(url, true)) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`Test Server is already running at ${url.toString()}, using it.\n`);
|
console.log(`Dev Server is already running at ${url.toString()}, using it.\n`);
|
||||||
process.env.PLAYWRIGHT_TEST_BASE_URL = url.toString();
|
process.env.PLAYWRIGHT_TEST_BASE_URL = url.toString();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,10 @@ export class Watcher {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
await this._fsWatcher?.close();
|
||||||
|
}
|
||||||
|
|
||||||
private _reportEventsIfAny() {
|
private _reportEventsIfAny() {
|
||||||
if (this._collector.length)
|
if (this._collector.length)
|
||||||
this._onChange(this._collector.slice());
|
this._onChange(this._collector.slice());
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import type { Metadata } from '../../types/test';
|
||||||
import type * as reporterTypes from '../../types/testReporter';
|
import type * as reporterTypes from '../../types/testReporter';
|
||||||
import type { ReporterV2 } from '../reporters/reporterV2';
|
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||||
|
|
||||||
|
// -- Reuse boundary -- Everything below this line is reused in the vscode extension.
|
||||||
|
|
||||||
export type StringIntern = (s: string) => string;
|
export type StringIntern = (s: string) => string;
|
||||||
export type JsonLocation = reporterTypes.Location;
|
export type JsonLocation = reporterTypes.Location;
|
||||||
export type JsonError = string;
|
export type JsonError = string;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
import type { TestServerInterface, TestServerInterfaceEvents } from '@testIsomorphic/testServerInterface';
|
import type { TestServerInterface, TestServerInterfaceEvents } from '@testIsomorphic/testServerInterface';
|
||||||
import * as events from './events';
|
import * as events from './events';
|
||||||
|
|
||||||
|
// -- Reuse boundary -- Everything below this line is reused in the vscode extension.
|
||||||
|
|
||||||
export class TestServerConnection implements TestServerInterface, TestServerInterfaceEvents {
|
export class TestServerConnection implements TestServerInterface, TestServerInterfaceEvents {
|
||||||
readonly onClose: events.Event<void>;
|
readonly onClose: events.Event<void>;
|
||||||
readonly onReport: events.Event<any>;
|
readonly onReport: events.Event<any>;
|
||||||
|
|
@ -155,6 +157,14 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
|
||||||
return await this._sendMessage('runGlobalTeardown', params);
|
return await this._sendMessage('runGlobalTeardown', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startDevServer(params: Parameters<TestServerInterface['startDevServer']>[0]): ReturnType<TestServerInterface['startDevServer']> {
|
||||||
|
return await this._sendMessage('startDevServer', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopDevServer(params: Parameters<TestServerInterface['stopDevServer']>[0]): ReturnType<TestServerInterface['stopDevServer']> {
|
||||||
|
return await this._sendMessage('stopDevServer', params);
|
||||||
|
}
|
||||||
|
|
||||||
async listFiles(params: Parameters<TestServerInterface['listFiles']>[0]): ReturnType<TestServerInterface['listFiles']> {
|
async listFiles(params: Parameters<TestServerInterface['listFiles']>[0]): ReturnType<TestServerInterface['listFiles']> {
|
||||||
return await this._sendMessage('listFiles', params);
|
return await this._sendMessage('listFiles', params);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ import type * as reporterTypes from '../../types/testReporter';
|
||||||
import type { Event } from './events';
|
import type { Event } from './events';
|
||||||
import type { JsonEvent } from './teleReceiver';
|
import type { JsonEvent } from './teleReceiver';
|
||||||
|
|
||||||
|
// -- Reuse boundary -- Everything below this line is reused in the vscode extension.
|
||||||
|
|
||||||
export type ReportEntry = JsonEvent;
|
export type ReportEntry = JsonEvent;
|
||||||
|
|
||||||
export interface TestServerInterface {
|
export interface TestServerInterface {
|
||||||
|
|
@ -52,6 +54,16 @@ export interface TestServerInterface {
|
||||||
status: reporterTypes.FullResult['status']
|
status: reporterTypes.FullResult['status']
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
startDevServer(params: {}): Promise<{
|
||||||
|
report: ReportEntry[];
|
||||||
|
status: reporterTypes.FullResult['status']
|
||||||
|
}>;
|
||||||
|
|
||||||
|
stopDevServer(params: {}): Promise<{
|
||||||
|
report: ReportEntry[];
|
||||||
|
status: reporterTypes.FullResult['status']
|
||||||
|
}>;
|
||||||
|
|
||||||
listFiles(params: {
|
listFiles(params: {
|
||||||
projects?: string[];
|
projects?: string[];
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
export type TestItemStatus = 'none' | 'running' | 'scheduled' | 'passed' | 'failed' | 'skipped';
|
export type TestItemStatus = 'none' | 'running' | 'scheduled' | 'passed' | 'failed' | 'skipped';
|
||||||
import type * as reporterTypes from '../../types/testReporter';
|
import type * as reporterTypes from '../../types/testReporter';
|
||||||
|
|
||||||
|
// -- Reuse boundary -- Everything below this line is reused in the vscode extension.
|
||||||
|
|
||||||
export type TreeItemBase = {
|
export type TreeItemBase = {
|
||||||
kind: 'root' | 'group' | 'case' | 'test',
|
kind: 'root' | 'group' | 'case' | 'test',
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,25 @@ function addFindRelatedTestFilesCommand(program: Command) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addDevServerCommand(program: Command) {
|
||||||
|
const command = program.command('dev-server', { hidden: true });
|
||||||
|
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)
|
||||||
|
return;
|
||||||
|
const { config } = configInternal;
|
||||||
|
const implementation = (config as any)['@playwright/test']?.['cli']?.['dev-server'];
|
||||||
|
if (implementation) {
|
||||||
|
await implementation(configInternal);
|
||||||
|
} else {
|
||||||
|
console.log(`DevServer is not available in the package you are using. Did you mean to use component testing?`);
|
||||||
|
gracefullyProcessExitDoNotHang(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function addTestServerCommand(program: Command) {
|
function addTestServerCommand(program: Command) {
|
||||||
const command = program.command('test-server', { hidden: true });
|
const command = program.command('test-server', { hidden: true });
|
||||||
command.description('start test server');
|
command.description('start test server');
|
||||||
|
|
@ -362,4 +381,5 @@ addListFilesCommand(program);
|
||||||
addMergeReportsCommand(program);
|
addMergeReportsCommand(program);
|
||||||
addClearCacheCommand(program);
|
addClearCacheCommand(program);
|
||||||
addFindRelatedTestFilesCommand(program);
|
addFindRelatedTestFilesCommand(program);
|
||||||
|
addDevServerCommand(program);
|
||||||
addTestServerCommand(program);
|
addTestServerCommand(program);
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import type * as teleReceiver from '../isomorphic/teleReceiver';
|
||||||
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
||||||
import type { ReporterV2 } from './reporterV2';
|
import type { ReporterV2 } from './reporterV2';
|
||||||
|
|
||||||
|
// -- Reuse boundary -- Everything below this line is reused in the vscode extension.
|
||||||
|
|
||||||
export type TeleReporterEmitterOptions = {
|
export type TeleReporterEmitterOptions = {
|
||||||
omitOutput?: boolean;
|
omitOutput?: boolean;
|
||||||
omitBuffers?: boolean;
|
omitBuffers?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ class TestServerDispatcher implements TestServerInterface {
|
||||||
private _serializer = require.resolve('./uiModeReporter');
|
private _serializer = require.resolve('./uiModeReporter');
|
||||||
private _watchTestDirs = false;
|
private _watchTestDirs = false;
|
||||||
private _closeOnDisconnect = false;
|
private _closeOnDisconnect = false;
|
||||||
|
private _devServerHandle: (() => Promise<void>) | undefined;
|
||||||
|
|
||||||
constructor(configFile: string | undefined) {
|
constructor(configFile: string | undefined) {
|
||||||
this._configFile = configFile;
|
this._configFile = configFile;
|
||||||
|
|
@ -172,6 +173,43 @@ class TestServerDispatcher implements TestServerInterface {
|
||||||
return { status, report: globalSetup?.report || [] };
|
return { status, report: globalSetup?.report || [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startDevServer(params: Parameters<TestServerInterface['startDevServer']>[0]): ReturnType<TestServerInterface['startDevServer']> {
|
||||||
|
if (this._devServerHandle)
|
||||||
|
return { status: 'failed', report: [] };
|
||||||
|
const { reporter, report } = await this._collectingReporter();
|
||||||
|
const { config, error } = await this._loadConfig(this._configFile);
|
||||||
|
if (!config) {
|
||||||
|
reporter.onError(error!);
|
||||||
|
return { status: 'failed', 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);
|
||||||
|
return { status: 'passed', report };
|
||||||
|
} catch (e) {
|
||||||
|
reporter.onError(serializeError(e));
|
||||||
|
return { status: 'failed', report };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopDevServer(params: Parameters<TestServerInterface['stopDevServer']>[0]): ReturnType<TestServerInterface['stopDevServer']> {
|
||||||
|
if (!this._devServerHandle)
|
||||||
|
return { status: 'failed', report: [] };
|
||||||
|
try {
|
||||||
|
await this._devServerHandle();
|
||||||
|
this._devServerHandle = undefined;
|
||||||
|
return { status: 'passed', report: [] };
|
||||||
|
} catch (e) {
|
||||||
|
const { reporter, report } = await this._collectingReporter();
|
||||||
|
reporter.onError(serializeError(e));
|
||||||
|
return { status: 'failed', report };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async listFiles(params: Parameters<TestServerInterface['listFiles']>[0]): ReturnType<TestServerInterface['listFiles']> {
|
async listFiles(params: Parameters<TestServerInterface['listFiles']>[0]): ReturnType<TestServerInterface['listFiles']> {
|
||||||
const { reporter, report } = await this._collectingReporter();
|
const { reporter, report } = await this._collectingReporter();
|
||||||
const { config, error } = await this._loadConfig(this._configFile);
|
const { config, error } = await this._loadConfig(this._configFile);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue