mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: add find-related-tests command (#29439)
This commit is contained in:
parent
f0244b8a76
commit
586d14f02c
@ -16,42 +16,30 @@
|
|||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type { FullConfigInternal } from 'playwright/lib/common/config';
|
|
||||||
import { ConfigLoader, resolveConfigFile } from 'playwright/lib/common/configLoader';
|
|
||||||
import { Watcher } from 'playwright/lib/fsWatcher';
|
import { Watcher } from 'playwright/lib/fsWatcher';
|
||||||
import { restartWithExperimentalTsEsm } from 'playwright/lib/program';
|
import { loadConfigFromFile } 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 } from './viteUtils';
|
import { createConfig, populateComponentsFromTests, resolveDirs, transformIndexFile } from './viteUtils';
|
||||||
import type { ComponentRegistry } from './viteUtils';
|
import type { ComponentRegistry } from './viteUtils';
|
||||||
|
|
||||||
export async function loadConfig(configFile: string): Promise<FullConfigInternal | null> {
|
|
||||||
const configFileOrDirectory = configFile ? path.resolve(process.cwd(), configFile) : process.cwd();
|
|
||||||
const resolvedConfigFile = resolveConfigFile(configFileOrDirectory);
|
|
||||||
if (restartWithExperimentalTsEsm(resolvedConfigFile))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
const configLoader = new ConfigLoader();
|
|
||||||
let config: FullConfigInternal;
|
|
||||||
if (resolvedConfigFile)
|
|
||||||
config = await configLoader.loadConfigFile(resolvedConfigFile);
|
|
||||||
else
|
|
||||||
config = await configLoader.loadEmptyConfig(configFileOrDirectory);
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runDevServer(configFile: string, registerSourceFile: string, frameworkPluginFactory: () => Promise<any>) {
|
export async function runDevServer(configFile: string, registerSourceFile: string, frameworkPluginFactory: () => Promise<any>) {
|
||||||
const config = await loadConfig(configFile);
|
const config = await loadConfigFromFile(configFile);
|
||||||
if (!config)
|
if (!config)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const runner = new Runner(config);
|
const runner = new Runner(config);
|
||||||
await runner.loadAllTests(true);
|
await runner.loadAllTests();
|
||||||
const componentRegistry: ComponentRegistry = new Map();
|
const componentRegistry: ComponentRegistry = new Map();
|
||||||
await populateComponentsFromTests(componentRegistry);
|
await populateComponentsFromTests(componentRegistry);
|
||||||
|
|
||||||
const dirs = await resolveDirs(config.configDir, config.config);
|
const dirs = await resolveDirs(config.configDir, config.config);
|
||||||
|
if (!dirs) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Template file playwright/index.html is missing.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
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);
|
||||||
viteConfig.plugins.push({
|
viteConfig.plugins.push({
|
||||||
|
|||||||
@ -16,55 +16,56 @@
|
|||||||
|
|
||||||
import type { Command } from 'playwright-core/lib/utilsBundle';
|
import type { Command } from 'playwright-core/lib/utilsBundle';
|
||||||
|
|
||||||
import fs from 'fs';
|
import path from 'path';
|
||||||
import { program } from 'playwright/lib/program';
|
import { program, removeFolder, setClearCacheCommandOverride, setFindRelatedTestsCommandOverride, withRunnerAndMutedWrite } from 'playwright/lib/program';
|
||||||
import { loadConfig, runDevServer } from './devServer';
|
import { runDevServer } from './devServer';
|
||||||
import { resolveDirs } from './viteUtils';
|
import { resolveDirs } from './viteUtils';
|
||||||
import { cacheDir } from 'playwright/lib/transform/compilationCache';
|
import { affectedTestFiles, cacheDir } from 'playwright/lib/transform/compilationCache';
|
||||||
|
import { loadConfigFromFile } from 'playwright/lib/common/configLoader';
|
||||||
|
import { buildBundle } from './vitePlugin';
|
||||||
export { program } from 'playwright/lib/program';
|
export { program } from 'playwright/lib/program';
|
||||||
|
|
||||||
let registerSourceFile: string;
|
let _framework: { registerSource: string, frameworkPluginFactory: () => Promise<any> };
|
||||||
let frameworkPluginFactory: () => Promise<any>;
|
|
||||||
|
|
||||||
export function initializePlugin(registerSource: string, factory: () => Promise<any>) {
|
export function initializePlugin(framework: { registerSource: string, frameworkPluginFactory: () => Promise<any> }) {
|
||||||
registerSourceFile = registerSource;
|
_framework = framework;
|
||||||
frameworkPluginFactory = factory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDevServerCommand(program: Command) {
|
function addDevServerCommand(program: Command) {
|
||||||
const command = program.command('dev-server');
|
const command = program.command('dev-server');
|
||||||
command.description('start dev server');
|
command.description('start dev server');
|
||||||
command.option('-c, --config <file>', `Configuration file.`);
|
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
|
||||||
command.action(options => {
|
command.action(options => {
|
||||||
runDevServer(options.config, registerSourceFile, frameworkPluginFactory);
|
runDevServer(options.config, _framework.registerSource, _framework.frameworkPluginFactory);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addClearCacheCommand(program: Command) {
|
setFindRelatedTestsCommandOverride(async (files, options) => {
|
||||||
const command = program.command('clear-caches');
|
await withRunnerAndMutedWrite(options.config, async (runner, config, configDir) => {
|
||||||
command.description('clears build and test caches');
|
const result = await runner.loadAllTests();
|
||||||
command.option('-c, --config <file>', `Configuration file.`);
|
if (result.status !== 'passed' || !result.suite)
|
||||||
command.action(async options => {
|
return { errors: result.errors };
|
||||||
const configFile = options.config;
|
await buildBundle({
|
||||||
const config = await loadConfig(configFile);
|
config,
|
||||||
if (!config)
|
configDir,
|
||||||
return;
|
suite: result.suite,
|
||||||
const { outDir } = await resolveDirs(config.configDir, config.config);
|
registerSourceFile: _framework.registerSource,
|
||||||
await removeFolder(outDir);
|
frameworkPluginFactory: _framework.frameworkPluginFactory,
|
||||||
await removeFolder(cacheDir);
|
});
|
||||||
|
const resolvedFiles = (files as string[]).map(file => path.resolve(process.cwd(), file));
|
||||||
|
return { relatedTests: affectedTestFiles(resolvedFiles) };
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
async function removeFolder(folder: string) {
|
setClearCacheCommandOverride(async options => {
|
||||||
try {
|
const configFile = options.config;
|
||||||
if (!fs.existsSync(folder))
|
const config = await loadConfigFromFile(configFile);
|
||||||
return;
|
if (!config)
|
||||||
// eslint-disable-next-line no-console
|
return;
|
||||||
console.log(`Removing ${await fs.promises.realpath(folder)}`);
|
const dirs = await resolveDirs(config.configDir, config.config);
|
||||||
await fs.promises.rm(folder, { recursive: true, force: true });
|
if (dirs)
|
||||||
} catch {
|
await removeFolder(dirs.outDir);
|
||||||
}
|
await removeFolder(cacheDir);
|
||||||
}
|
});
|
||||||
|
|
||||||
addDevServerCommand(program);
|
addDevServerCommand(program);
|
||||||
addClearCacheCommand(program);
|
|
||||||
|
|||||||
@ -52,103 +52,18 @@ export function createPlugin(
|
|||||||
},
|
},
|
||||||
|
|
||||||
begin: async (suite: Suite) => {
|
begin: async (suite: Suite) => {
|
||||||
{
|
const result = await buildBundle({
|
||||||
// Detect a running dev server and use it if available.
|
config,
|
||||||
const endpoint = resolveEndpoint(config);
|
configDir,
|
||||||
const protocol = endpoint.https ? 'https:' : 'http:';
|
suite,
|
||||||
const url = new URL(`${protocol}//${endpoint.host}:${endpoint.port}`);
|
registerSourceFile,
|
||||||
if (await isURLAvailable(url, true)) {
|
frameworkPluginFactory: frameworkPluginFactory,
|
||||||
// eslint-disable-next-line no-console
|
});
|
||||||
console.log(`Test Server is already running at ${url.toString()}, using it.\n`);
|
if (!result)
|
||||||
process.env.PLAYWRIGHT_TEST_BASE_URL = url.toString();
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dirs = await resolveDirs(configDir, config);
|
|
||||||
const buildInfoFile = path.join(dirs.outDir, 'metainfo.json');
|
|
||||||
|
|
||||||
let buildExists = false;
|
|
||||||
let buildInfo: BuildInfo;
|
|
||||||
|
|
||||||
const registerSource = injectedSource + '\n' + await fs.promises.readFile(registerSourceFile, 'utf-8');
|
|
||||||
const registerSourceHash = calculateSha1(registerSource);
|
|
||||||
|
|
||||||
const { version: viteVersion, build, preview, mergeConfig } = await import('vite');
|
|
||||||
|
|
||||||
try {
|
|
||||||
buildInfo = JSON.parse(await fs.promises.readFile(buildInfoFile, 'utf-8')) as BuildInfo;
|
|
||||||
assert(buildInfo.version === playwrightVersion);
|
|
||||||
assert(buildInfo.viteVersion === viteVersion);
|
|
||||||
assert(buildInfo.registerSourceHash === registerSourceHash);
|
|
||||||
buildExists = true;
|
|
||||||
} catch (e) {
|
|
||||||
buildInfo = {
|
|
||||||
version: playwrightVersion,
|
|
||||||
viteVersion,
|
|
||||||
registerSourceHash,
|
|
||||||
components: [],
|
|
||||||
sources: {},
|
|
||||||
deps: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
log('build exists:', buildExists);
|
|
||||||
|
|
||||||
const componentRegistry: ComponentRegistry = new Map();
|
|
||||||
const componentsByImportingFile = new Map<string, string[]>();
|
|
||||||
// 1. Populate component registry based on tests' component imports.
|
|
||||||
await populateComponentsFromTests(componentRegistry, componentsByImportingFile);
|
|
||||||
|
|
||||||
// 2. Check if the set of required components has changed.
|
|
||||||
const hasNewComponents = await checkNewComponents(buildInfo, componentRegistry);
|
|
||||||
log('has new components:', hasNewComponents);
|
|
||||||
|
|
||||||
// 3. Check component sources.
|
|
||||||
const sourcesDirty = !buildExists || hasNewComponents || await checkSources(buildInfo);
|
|
||||||
log('sourcesDirty:', sourcesDirty);
|
|
||||||
|
|
||||||
// 4. Update component info.
|
|
||||||
buildInfo.components = [...componentRegistry.values()];
|
|
||||||
|
|
||||||
const jsxInJS = hasJSComponents(buildInfo.components);
|
|
||||||
const viteConfig = await createConfig(dirs, config, frameworkPluginFactory, jsxInJS);
|
|
||||||
|
|
||||||
if (sourcesDirty) {
|
|
||||||
// Only add out own plugin when we actually build / transform.
|
|
||||||
log('build');
|
|
||||||
const depsCollector = new Map<string, string[]>();
|
|
||||||
const buildConfig = mergeConfig(viteConfig, {
|
|
||||||
plugins: [vitePlugin(registerSource, dirs.templateDir, buildInfo, componentRegistry, depsCollector)]
|
|
||||||
});
|
|
||||||
await build(buildConfig);
|
|
||||||
buildInfo.deps = Object.fromEntries(depsCollector.entries());
|
|
||||||
|
|
||||||
// Update dependencies based on the vite build.
|
|
||||||
for (const projectSuite of suite.suites) {
|
|
||||||
for (const fileSuite of projectSuite.suites) {
|
|
||||||
// For every test file...
|
|
||||||
const testFile = fileSuite.location!.file;
|
|
||||||
const deps = new Set<string>();
|
|
||||||
// Collect its JS dependencies (helpers).
|
|
||||||
for (const file of [testFile, ...(internalDependenciesForTestFile(testFile) || [])]) {
|
|
||||||
// For each helper, get all the imported components.
|
|
||||||
for (const componentFile of componentsByImportingFile.get(file) || []) {
|
|
||||||
// For each component, get all the dependencies.
|
|
||||||
for (const d of depsCollector.get(componentFile) || [])
|
|
||||||
deps.add(d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Now we have test file => all components along with dependencies.
|
|
||||||
setExternalDependencies(testFile, [...deps]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNewComponents || sourcesDirty) {
|
|
||||||
log('write manifest');
|
|
||||||
await fs.promises.writeFile(buildInfoFile, JSON.stringify(buildInfo, undefined, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const { viteConfig } = result;
|
||||||
|
const { preview } = await import('vite');
|
||||||
const previewServer = await preview(viteConfig);
|
const previewServer = await preview(viteConfig);
|
||||||
stoppableServer = stoppable(previewServer.httpServer as http.Server, 0);
|
stoppableServer = stoppable(previewServer.httpServer as http.Server, 0);
|
||||||
const isAddressInfo = (x: any): x is AddressInfo => x?.address;
|
const isAddressInfo = (x: any): x is AddressInfo => x?.address;
|
||||||
@ -181,6 +96,120 @@ type BuildInfo = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function buildBundle(options: {
|
||||||
|
config: FullConfig,
|
||||||
|
configDir: string,
|
||||||
|
suite: Suite,
|
||||||
|
registerSourceFile: string,
|
||||||
|
frameworkPluginFactory?: () => Promise<Plugin>
|
||||||
|
}): Promise<{ buildInfo: BuildInfo, viteConfig: Record<string, any> } | null> {
|
||||||
|
{
|
||||||
|
// Detect a running dev server and use it if available.
|
||||||
|
const endpoint = resolveEndpoint(options.config);
|
||||||
|
const protocol = endpoint.https ? 'https:' : 'http:';
|
||||||
|
const url = new URL(`${protocol}//${endpoint.host}:${endpoint.port}`);
|
||||||
|
if (await isURLAvailable(url, true)) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Test Server is already running at ${url.toString()}, using it.\n`);
|
||||||
|
process.env.PLAYWRIGHT_TEST_BASE_URL = url.toString();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirs = await resolveDirs(options.configDir, options.config);
|
||||||
|
if (!dirs) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Template file playwright/index.html is missing.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildInfoFile = path.join(dirs.outDir, 'metainfo.json');
|
||||||
|
|
||||||
|
let buildExists = false;
|
||||||
|
let buildInfo: BuildInfo;
|
||||||
|
|
||||||
|
const registerSource = injectedSource + '\n' + await fs.promises.readFile(options.registerSourceFile, 'utf-8');
|
||||||
|
const registerSourceHash = calculateSha1(registerSource);
|
||||||
|
|
||||||
|
const { version: viteVersion, build, mergeConfig } = await import('vite');
|
||||||
|
|
||||||
|
try {
|
||||||
|
buildInfo = JSON.parse(await fs.promises.readFile(buildInfoFile, 'utf-8')) as BuildInfo;
|
||||||
|
assert(buildInfo.version === playwrightVersion);
|
||||||
|
assert(buildInfo.viteVersion === viteVersion);
|
||||||
|
assert(buildInfo.registerSourceHash === registerSourceHash);
|
||||||
|
buildExists = true;
|
||||||
|
} catch (e) {
|
||||||
|
buildInfo = {
|
||||||
|
version: playwrightVersion,
|
||||||
|
viteVersion,
|
||||||
|
registerSourceHash,
|
||||||
|
components: [],
|
||||||
|
sources: {},
|
||||||
|
deps: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
log('build exists:', buildExists);
|
||||||
|
|
||||||
|
const componentRegistry: ComponentRegistry = new Map();
|
||||||
|
const componentsByImportingFile = new Map<string, string[]>();
|
||||||
|
// 1. Populate component registry based on tests' component imports.
|
||||||
|
await populateComponentsFromTests(componentRegistry, componentsByImportingFile);
|
||||||
|
|
||||||
|
// 2. Check if the set of required components has changed.
|
||||||
|
const hasNewComponents = await checkNewComponents(buildInfo, componentRegistry);
|
||||||
|
log('has new components:', hasNewComponents);
|
||||||
|
|
||||||
|
// 3. Check component sources.
|
||||||
|
const sourcesDirty = !buildExists || hasNewComponents || await checkSources(buildInfo);
|
||||||
|
log('sourcesDirty:', sourcesDirty);
|
||||||
|
|
||||||
|
// 4. Update component info.
|
||||||
|
buildInfo.components = [...componentRegistry.values()];
|
||||||
|
|
||||||
|
const jsxInJS = hasJSComponents(buildInfo.components);
|
||||||
|
const viteConfig = await createConfig(dirs, options.config, options.frameworkPluginFactory, jsxInJS);
|
||||||
|
|
||||||
|
if (sourcesDirty) {
|
||||||
|
// Only add out own plugin when we actually build / transform.
|
||||||
|
log('build');
|
||||||
|
const depsCollector = new Map<string, string[]>();
|
||||||
|
const buildConfig = mergeConfig(viteConfig, {
|
||||||
|
plugins: [vitePlugin(registerSource, dirs.templateDir, buildInfo, componentRegistry, depsCollector)]
|
||||||
|
});
|
||||||
|
await build(buildConfig);
|
||||||
|
buildInfo.deps = Object.fromEntries(depsCollector.entries());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Update dependencies based on the vite build.
|
||||||
|
for (const projectSuite of options.suite.suites) {
|
||||||
|
for (const fileSuite of projectSuite.suites) {
|
||||||
|
// For every test file...
|
||||||
|
const testFile = fileSuite.location!.file;
|
||||||
|
const deps = new Set<string>();
|
||||||
|
// Collect its JS dependencies (helpers).
|
||||||
|
for (const file of [testFile, ...(internalDependenciesForTestFile(testFile) || [])]) {
|
||||||
|
// For each helper, get all the imported components.
|
||||||
|
for (const componentFile of componentsByImportingFile.get(file) || []) {
|
||||||
|
// For each component, get all the dependencies.
|
||||||
|
for (const d of buildInfo.deps[componentFile] || [])
|
||||||
|
deps.add(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now we have test file => all components along with dependencies.
|
||||||
|
setExternalDependencies(testFile, [...deps]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasNewComponents || sourcesDirty) {
|
||||||
|
log('write manifest');
|
||||||
|
await fs.promises.writeFile(buildInfoFile, JSON.stringify(buildInfo, undefined, 2));
|
||||||
|
}
|
||||||
|
return { buildInfo, viteConfig };
|
||||||
|
}
|
||||||
|
|
||||||
async function checkSources(buildInfo: BuildInfo): Promise<boolean> {
|
async function checkSources(buildInfo: BuildInfo): Promise<boolean> {
|
||||||
for (const [source, sourceInfo] of Object.entries(buildInfo.sources)) {
|
for (const [source, sourceInfo] of Object.entries(buildInfo.sources)) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -39,13 +39,15 @@ export type ComponentDirs = {
|
|||||||
templateDir: string;
|
templateDir: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function resolveDirs(configDir: string, config: FullConfig): Promise<ComponentDirs> {
|
export async function resolveDirs(configDir: string, config: FullConfig): Promise<ComponentDirs | null> {
|
||||||
const use = config.projects[0].use as CtConfig;
|
const use = config.projects[0].use as CtConfig;
|
||||||
// FIXME: use build plugin to determine html location to resolve this.
|
// FIXME: use build plugin to determine html location to resolve this.
|
||||||
// TemplateDir must be relative, otherwise we can't move the final index.html into its target location post-build.
|
// TemplateDir must be relative, otherwise we can't move the final index.html into its target location post-build.
|
||||||
// This regressed in https://github.com/microsoft/playwright/pull/26526
|
// This regressed in https://github.com/microsoft/playwright/pull/26526
|
||||||
const relativeTemplateDir = use.ctTemplateDir || 'playwright';
|
const relativeTemplateDir = use.ctTemplateDir || 'playwright';
|
||||||
const templateDir = await fs.promises.realpath(path.normalize(path.join(configDir, relativeTemplateDir)));
|
const templateDir = await fs.promises.realpath(path.normalize(path.join(configDir, relativeTemplateDir))).catch(() => undefined);
|
||||||
|
if (!templateDir)
|
||||||
|
return null;
|
||||||
const outDir = use.ctCacheDir ? path.resolve(configDir, use.ctCacheDir) : path.resolve(templateDir, '.cache');
|
const outDir = use.ctCacheDir ? path.resolve(configDir, use.ctCacheDir) : path.resolve(templateDir, '.cache');
|
||||||
return {
|
return {
|
||||||
configDir,
|
configDir,
|
||||||
|
|||||||
@ -15,8 +15,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
||||||
|
const { _framework } = require('./index');
|
||||||
|
|
||||||
initializePlugin(path.join(__dirname, 'registerSource.mjs'), () => import('@vitejs/plugin-react').then(plugin => plugin.default()))
|
initializePlugin(_framework);
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|||||||
@ -17,13 +17,14 @@
|
|||||||
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const registerSource = path.join(__dirname, 'registerSource.mjs');
|
||||||
|
const frameworkPluginFactory = () => import('@vitejs/plugin-react').then(plugin => plugin.default());
|
||||||
|
|
||||||
const plugin = () => {
|
const plugin = () => {
|
||||||
// Only fetch upon request to avoid resolution in workers.
|
// Only fetch upon request to avoid resolution in workers.
|
||||||
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
||||||
return createPlugin(
|
return createPlugin(registerSource, frameworkPluginFactory);
|
||||||
path.join(__dirname, 'registerSource.mjs'),
|
|
||||||
() => import('@vitejs/plugin-react').then(plugin => plugin.default()));
|
|
||||||
};
|
};
|
||||||
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
||||||
|
|
||||||
module.exports = { test, expect, devices, defineConfig };
|
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } };
|
||||||
|
|||||||
@ -15,5 +15,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
||||||
|
const { _framework } = require('./index');
|
||||||
|
|
||||||
|
initializePlugin(_framework);
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|||||||
@ -17,13 +17,14 @@
|
|||||||
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const registerSource = path.join(__dirname, 'registerSource.mjs');
|
||||||
|
const frameworkPluginFactory = () => import('@vitejs/plugin-react').then(plugin => plugin.default());
|
||||||
|
|
||||||
const plugin = () => {
|
const plugin = () => {
|
||||||
// Only fetch upon request to avoid resolution in workers.
|
// Only fetch upon request to avoid resolution in workers.
|
||||||
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
||||||
return createPlugin(
|
return createPlugin(registerSource, frameworkPluginFactory);
|
||||||
path.join(__dirname, 'registerSource.mjs'),
|
|
||||||
() => import('@vitejs/plugin-react').then(plugin => plugin.default()));
|
|
||||||
};
|
};
|
||||||
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
||||||
|
|
||||||
module.exports = { test, expect, devices, defineConfig };
|
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } };
|
||||||
|
|||||||
@ -15,5 +15,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
||||||
|
const { _framework } = require('./index');
|
||||||
|
|
||||||
|
initializePlugin(_framework);
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|||||||
@ -17,13 +17,14 @@
|
|||||||
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const registerSource = path.join(__dirname, 'registerSource.mjs');
|
||||||
|
const frameworkPluginFactory = () => import('vite-plugin-solid').then(plugin => plugin.default());
|
||||||
|
|
||||||
const plugin = () => {
|
const plugin = () => {
|
||||||
// Only fetch upon request to avoid resolution in workers.
|
// Only fetch upon request to avoid resolution in workers.
|
||||||
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
||||||
return createPlugin(
|
return createPlugin(registerSource, frameworkPluginFactory);
|
||||||
path.join(__dirname, 'registerSource.mjs'),
|
|
||||||
() => import('vite-plugin-solid').then(plugin => plugin.default()));
|
|
||||||
};
|
};
|
||||||
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
||||||
|
|
||||||
module.exports = { test, expect, devices, defineConfig };
|
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } };
|
||||||
|
|||||||
@ -15,5 +15,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
||||||
|
const { _framework } = require('./index');
|
||||||
|
|
||||||
|
initializePlugin(_framework);
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|||||||
@ -17,13 +17,14 @@
|
|||||||
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const registerSource = path.join(__dirname, 'registerSource.mjs');
|
||||||
|
const frameworkPluginFactory = () => import('@sveltejs/vite-plugin-svelte').then(plugin => plugin.svelte());
|
||||||
|
|
||||||
const plugin = () => {
|
const plugin = () => {
|
||||||
// Only fetch upon request to avoid resolution in workers.
|
// Only fetch upon request to avoid resolution in workers.
|
||||||
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
||||||
return createPlugin(
|
return createPlugin(registerSource, frameworkPluginFactory);
|
||||||
path.join(__dirname, 'registerSource.mjs'),
|
|
||||||
() => import('@sveltejs/vite-plugin-svelte').then(plugin => plugin.svelte()));
|
|
||||||
};
|
};
|
||||||
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
||||||
|
|
||||||
module.exports = { test, expect, devices, defineConfig };
|
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } };
|
||||||
|
|||||||
@ -15,5 +15,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
||||||
|
const { _framework } = require('./index');
|
||||||
|
|
||||||
|
initializePlugin(_framework);
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|||||||
@ -17,13 +17,14 @@
|
|||||||
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const registerSource = path.join(__dirname, 'registerSource.mjs');
|
||||||
|
const frameworkPluginFactory = () => import('@vitejs/plugin-vue').then(plugin => plugin.default());
|
||||||
|
|
||||||
const plugin = () => {
|
const plugin = () => {
|
||||||
// Only fetch upon request to avoid resolution in workers.
|
// Only fetch upon request to avoid resolution in workers.
|
||||||
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
||||||
return createPlugin(
|
return createPlugin(registerSource, frameworkPluginFactory);
|
||||||
path.join(__dirname, 'registerSource.mjs'),
|
};
|
||||||
() => import('@vitejs/plugin-vue').then(plugin => plugin.default()));
|
|
||||||
}
|
|
||||||
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
||||||
|
|
||||||
module.exports = { test, expect, devices, defineConfig };
|
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } };
|
||||||
|
|||||||
@ -15,5 +15,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
||||||
|
const { _framework } = require('./index');
|
||||||
|
|
||||||
|
initializePlugin(_framework);
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|||||||
@ -17,13 +17,14 @@
|
|||||||
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
const { test, expect, devices, defineConfig: originalDefineConfig } = require('@playwright/experimental-ct-core');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const registerSource = path.join(__dirname, 'registerSource.mjs');
|
||||||
|
const frameworkPluginFactory = () => import('@vitejs/plugin-vue2').then(plugin => plugin.default());
|
||||||
|
|
||||||
const plugin = () => {
|
const plugin = () => {
|
||||||
// Only fetch upon request to avoid resolution in workers.
|
// Only fetch upon request to avoid resolution in workers.
|
||||||
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
|
||||||
return createPlugin(
|
return createPlugin(registerSource, frameworkPluginFactory);
|
||||||
path.join(__dirname, 'registerSource.mjs'),
|
|
||||||
() => import('@vitejs/plugin-vue2').then(plugin => plugin.default()));
|
|
||||||
};
|
};
|
||||||
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
|
||||||
|
|
||||||
module.exports = { test, expect, devices, defineConfig };
|
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } };
|
||||||
|
|||||||
@ -16,15 +16,16 @@
|
|||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { isRegExp } from 'playwright-core/lib/utils';
|
import { gracefullyProcessExitDoNotHang, isRegExp } from 'playwright-core/lib/utils';
|
||||||
import type { ConfigCLIOverrides, SerializedConfig } from './ipc';
|
import type { ConfigCLIOverrides, SerializedConfig } from './ipc';
|
||||||
import { requireOrImport } from '../transform/transform';
|
import { requireOrImport } from '../transform/transform';
|
||||||
import type { Config, Project } from '../../types/test';
|
import type { Config, Project } from '../../types/test';
|
||||||
import { errorWithFile } from '../util';
|
import { errorWithFile, fileIsModule } from '../util';
|
||||||
import { setCurrentConfig } from './globals';
|
import { setCurrentConfig } from './globals';
|
||||||
import { FullConfigInternal } from './config';
|
import { FullConfigInternal } from './config';
|
||||||
import { addToCompilationCache } from '../transform/compilationCache';
|
import { addToCompilationCache } from '../transform/compilationCache';
|
||||||
import { initializeEsmLoader } from './esmLoaderHost';
|
import { initializeEsmLoader, registerESMLoader } from './esmLoaderHost';
|
||||||
|
import { execArgvWithExperimentalLoaderOptions, execArgvWithoutExperimentalLoaderOptions } from '../transform/esmUtils';
|
||||||
|
|
||||||
const kDefineConfigWasUsed = Symbol('defineConfigWasUsed');
|
const kDefineConfigWasUsed = Symbol('defineConfigWasUsed');
|
||||||
export const defineConfig = (...configs: any[]) => {
|
export const defineConfig = (...configs: any[]) => {
|
||||||
@ -339,3 +340,55 @@ export function resolveConfigFile(configFileOrDirectory: string): string | null
|
|||||||
return configFile!;
|
return configFile!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function loadConfigFromFile(configFile: string | undefined, overrides?: ConfigCLIOverrides, ignoreDeps?: boolean): Promise<FullConfigInternal | null> {
|
||||||
|
const configFileOrDirectory = configFile ? path.resolve(process.cwd(), configFile) : process.cwd();
|
||||||
|
const resolvedConfigFile = resolveConfigFile(configFileOrDirectory);
|
||||||
|
if (restartWithExperimentalTsEsm(resolvedConfigFile))
|
||||||
|
return null;
|
||||||
|
const configLoader = new ConfigLoader(overrides);
|
||||||
|
let config: FullConfigInternal;
|
||||||
|
if (resolvedConfigFile)
|
||||||
|
config = await configLoader.loadConfigFile(resolvedConfigFile, ignoreDeps);
|
||||||
|
else
|
||||||
|
config = await configLoader.loadEmptyConfig(configFileOrDirectory);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function restartWithExperimentalTsEsm(configFile: string | null): boolean {
|
||||||
|
const nodeVersion = +process.versions.node.split('.')[0];
|
||||||
|
// New experimental loader is only supported on Node 16+.
|
||||||
|
if (nodeVersion < 16)
|
||||||
|
return false;
|
||||||
|
if (!configFile)
|
||||||
|
return false;
|
||||||
|
if (process.env.PW_DISABLE_TS_ESM)
|
||||||
|
return false;
|
||||||
|
// Node.js < 20
|
||||||
|
if ((globalThis as any).__esmLoaderPortPreV20) {
|
||||||
|
// clear execArgv after restart, so that childProcess.fork in user code does not inherit our loader.
|
||||||
|
process.execArgv = execArgvWithoutExperimentalLoaderOptions();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!fileIsModule(configFile))
|
||||||
|
return false;
|
||||||
|
// Node.js < 20
|
||||||
|
if (!require('node:module').register) {
|
||||||
|
const innerProcess = (require('child_process') as typeof import('child_process')).fork(require.resolve('../../cli'), process.argv.slice(2), {
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PW_TS_ESM_LEGACY_LOADER_ON: '1',
|
||||||
|
},
|
||||||
|
execArgv: execArgvWithExperimentalLoaderOptions(),
|
||||||
|
});
|
||||||
|
|
||||||
|
innerProcess.on('close', (code: number | null) => {
|
||||||
|
if (code !== 0 && code !== null)
|
||||||
|
gracefullyProcessExitDoNotHang(code);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Nodejs >= 21
|
||||||
|
registerESMLoader();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@ -21,21 +21,20 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Runner } from './runner/runner';
|
import { Runner } from './runner/runner';
|
||||||
import { stopProfiling, startProfiling, gracefullyProcessExitDoNotHang } from 'playwright-core/lib/utils';
|
import { stopProfiling, startProfiling, gracefullyProcessExitDoNotHang } from 'playwright-core/lib/utils';
|
||||||
import { fileIsModule, serializeError } from './util';
|
import { serializeError } from './util';
|
||||||
import { showHTMLReport } from './reporters/html';
|
import { showHTMLReport } from './reporters/html';
|
||||||
import { createMergedReport } from './reporters/merge';
|
import { createMergedReport } from './reporters/merge';
|
||||||
import { ConfigLoader, resolveConfigFile } from './common/configLoader';
|
import { ConfigLoader, loadConfigFromFile } from './common/configLoader';
|
||||||
import type { ConfigCLIOverrides } from './common/ipc';
|
import type { ConfigCLIOverrides } from './common/ipc';
|
||||||
import type { FullResult, TestError } from '../types/testReporter';
|
import type { FullResult, TestError } from '../types/testReporter';
|
||||||
import type { TraceMode } from '../types/test';
|
import type { FullConfig, TraceMode } from '../types/test';
|
||||||
import { builtInReporters, defaultReporter, defaultTimeout } from './common/config';
|
import { builtInReporters, defaultReporter, defaultTimeout } from './common/config';
|
||||||
import type { FullConfigInternal } from './common/config';
|
import type { FullConfigInternal } from './common/config';
|
||||||
import { program } from 'playwright-core/lib/cli/program';
|
import { program } from 'playwright-core/lib/cli/program';
|
||||||
export { program } from 'playwright-core/lib/cli/program';
|
export { program } from 'playwright-core/lib/cli/program';
|
||||||
import type { ReporterDescription } from '../types/test';
|
import type { ReporterDescription } from '../types/test';
|
||||||
import { prepareErrorStack } from './reporters/base';
|
import { prepareErrorStack } from './reporters/base';
|
||||||
import { registerESMLoader } from './common/esmLoaderHost';
|
import { affectedTestFiles, cacheDir } from './transform/compilationCache';
|
||||||
import { execArgvWithExperimentalLoaderOptions, execArgvWithoutExperimentalLoaderOptions } from './transform/esmUtils';
|
|
||||||
|
|
||||||
function addTestCommand(program: Command) {
|
function addTestCommand(program: Command) {
|
||||||
const command = program.command('test [test-filter...]');
|
const command = program.command('test [test-filter...]');
|
||||||
@ -76,6 +75,54 @@ function addListFilesCommand(program: Command) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let clearCacheCommandOverride: (opts: any) => Promise<void>;
|
||||||
|
export function setClearCacheCommandOverride(body: (opts: any) => Promise<void>) {
|
||||||
|
clearCacheCommandOverride = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addClearCacheCommand(program: Command) {
|
||||||
|
const command = program.command('clear-cache');
|
||||||
|
command.description('clears build and test caches');
|
||||||
|
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
|
||||||
|
command.action(async opts => {
|
||||||
|
if (clearCacheCommandOverride)
|
||||||
|
return clearCacheCommandOverride(opts);
|
||||||
|
await removeFolder(cacheDir);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeFolder(folder: string) {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(folder))
|
||||||
|
return;
|
||||||
|
console.log(`Removing ${await fs.promises.realpath(folder)}`);
|
||||||
|
await fs.promises.rm(folder, { recursive: true, force: true });
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let findRelatedTestsCommandOverride: (files: string[], opts: any) => Promise<void>;
|
||||||
|
export function setFindRelatedTestsCommandOverride(body: (files: string[], opts: any) => Promise<void>) {
|
||||||
|
findRelatedTestsCommandOverride = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFindRelatedTestsCommand(program: Command) {
|
||||||
|
const command = program.command('find-related-tests [source-files...]');
|
||||||
|
command.description('Returns the list of related tests to the given files');
|
||||||
|
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
|
||||||
|
command.action(async (files, options) => {
|
||||||
|
if (findRelatedTestsCommandOverride)
|
||||||
|
return findRelatedTestsCommandOverride(files, options);
|
||||||
|
await withRunnerAndMutedWrite(options.config, async runner => {
|
||||||
|
const result = await runner.loadAllTests();
|
||||||
|
if (result.status !== 'passed' || !result.suite)
|
||||||
|
return { errors: result.errors };
|
||||||
|
const resolvedFiles = (files as string[]).map(file => path.resolve(process.cwd(), file));
|
||||||
|
return { relatedTests: affectedTestFiles(resolvedFiles) };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function addShowReportCommand(program: Command) {
|
function addShowReportCommand(program: Command) {
|
||||||
const command = program.command('show-report [report]');
|
const command = program.command('show-report [report]');
|
||||||
command.description('show HTML report');
|
command.description('show HTML report');
|
||||||
@ -115,21 +162,10 @@ Examples:
|
|||||||
|
|
||||||
async function runTests(args: string[], opts: { [key: string]: any }) {
|
async function runTests(args: string[], opts: { [key: string]: any }) {
|
||||||
await startProfiling();
|
await startProfiling();
|
||||||
|
const config = await loadConfigFromFile(opts.config, overridesFromOptions(opts), opts.deps === false);
|
||||||
// When no --config option is passed, let's look for the config file in the current directory.
|
if (!config)
|
||||||
const configFileOrDirectory = opts.config ? path.resolve(process.cwd(), opts.config) : process.cwd();
|
|
||||||
const resolvedConfigFile = resolveConfigFile(configFileOrDirectory);
|
|
||||||
if (restartWithExperimentalTsEsm(resolvedConfigFile))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const overrides = overridesFromOptions(opts);
|
|
||||||
const configLoader = new ConfigLoader(overrides);
|
|
||||||
let config: FullConfigInternal;
|
|
||||||
if (resolvedConfigFile)
|
|
||||||
config = await configLoader.loadConfigFile(resolvedConfigFile, opts.deps === false);
|
|
||||||
else
|
|
||||||
config = await configLoader.loadEmptyConfig(configFileOrDirectory);
|
|
||||||
|
|
||||||
config.cliArgs = args;
|
config.cliArgs = args;
|
||||||
config.cliGrep = opts.grep as string | undefined;
|
config.cliGrep = opts.grep as string | undefined;
|
||||||
config.cliGrepInvert = opts.grepInvert as string | undefined;
|
config.cliGrepInvert = opts.grepInvert as string | undefined;
|
||||||
@ -151,47 +187,44 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
|
|||||||
gracefullyProcessExitDoNotHang(exitCode);
|
gracefullyProcessExitDoNotHang(exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listTestFiles(opts: { [key: string]: any }) {
|
export async function withRunnerAndMutedWrite(configFile: string | undefined, callback: (runner: Runner, config: FullConfig, configDir: string) => Promise<any>) {
|
||||||
// Redefine process.stdout.write in case config decides to pollute stdio.
|
// Redefine process.stdout.write in case config decides to pollute stdio.
|
||||||
const stdoutWrite = process.stdout.write.bind(process.stdout);
|
const stdoutWrite = process.stdout.write.bind(process.stdout);
|
||||||
process.stdout.write = (() => {}) as any;
|
process.stdout.write = ((a: any, b: any, c: any) => process.stderr.write(a, b, c)) as any;
|
||||||
process.stderr.write = (() => {}) as any;
|
|
||||||
const configFileOrDirectory = opts.config ? path.resolve(process.cwd(), opts.config) : process.cwd();
|
|
||||||
const resolvedConfigFile = resolveConfigFile(configFileOrDirectory)!;
|
|
||||||
if (restartWithExperimentalTsEsm(resolvedConfigFile))
|
|
||||||
return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const configLoader = new ConfigLoader();
|
const config = await loadConfigFromFile(configFile);
|
||||||
const config = await configLoader.loadConfigFile(resolvedConfigFile);
|
if (!config)
|
||||||
|
return;
|
||||||
const runner = new Runner(config);
|
const runner = new Runner(config);
|
||||||
const report = await runner.listTestFiles(opts.project);
|
const result = await callback(runner, config.config, config.configDir);
|
||||||
stdoutWrite(JSON.stringify(report), () => {
|
stdoutWrite(JSON.stringify(result, undefined, 2), () => {
|
||||||
gracefullyProcessExitDoNotHang(0);
|
gracefullyProcessExitDoNotHang(0);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error: TestError = serializeError(e);
|
const error: TestError = serializeError(e);
|
||||||
error.location = prepareErrorStack(e.stack).location;
|
error.location = prepareErrorStack(e.stack).location;
|
||||||
stdoutWrite(JSON.stringify({ error }), () => {
|
stdoutWrite(JSON.stringify({ error }, undefined, 2), () => {
|
||||||
gracefullyProcessExitDoNotHang(0);
|
gracefullyProcessExitDoNotHang(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function listTestFiles(opts: { [key: string]: any }) {
|
||||||
|
await withRunnerAndMutedWrite(opts.config, async runner => runner.listTestFiles(opts.project));
|
||||||
|
}
|
||||||
|
|
||||||
async function mergeReports(reportDir: string | undefined, opts: { [key: string]: any }) {
|
async function mergeReports(reportDir: string | undefined, opts: { [key: string]: any }) {
|
||||||
let configFile = opts.config;
|
const configFile = opts.config;
|
||||||
|
let config: FullConfigInternal | null;
|
||||||
if (configFile) {
|
if (configFile) {
|
||||||
configFile = path.resolve(process.cwd(), configFile);
|
config = await loadConfigFromFile(configFile);
|
||||||
if (!fs.existsSync(configFile))
|
} else {
|
||||||
throw new Error(`${configFile} does not exist`);
|
const configLoader = new ConfigLoader();
|
||||||
if (!fs.statSync(configFile).isFile())
|
config = await configLoader.loadEmptyConfig(process.cwd());
|
||||||
throw new Error(`${configFile} is not a file`);
|
|
||||||
}
|
}
|
||||||
if (restartWithExperimentalTsEsm(configFile))
|
if (!config)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const configLoader = new ConfigLoader();
|
|
||||||
const config = await (configFile ? configLoader.loadConfigFile(configFile) : configLoader.loadEmptyConfig(process.cwd()));
|
|
||||||
const dir = path.resolve(process.cwd(), reportDir || '');
|
const dir = path.resolve(process.cwd(), reportDir || '');
|
||||||
const dirStat = await fs.promises.stat(dir).catch(e => null);
|
const dirStat = await fs.promises.stat(dir).catch(e => null);
|
||||||
if (!dirStat)
|
if (!dirStat)
|
||||||
@ -272,44 +305,6 @@ function resolveReporter(id: string) {
|
|||||||
return require.resolve(id, { paths: [process.cwd()] });
|
return require.resolve(id, { paths: [process.cwd()] });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function restartWithExperimentalTsEsm(configFile: string | null): boolean {
|
|
||||||
const nodeVersion = +process.versions.node.split('.')[0];
|
|
||||||
// New experimental loader is only supported on Node 16+.
|
|
||||||
if (nodeVersion < 16)
|
|
||||||
return false;
|
|
||||||
if (!configFile)
|
|
||||||
return false;
|
|
||||||
if (process.env.PW_DISABLE_TS_ESM)
|
|
||||||
return false;
|
|
||||||
// Node.js < 20
|
|
||||||
if ((globalThis as any).__esmLoaderPortPreV20) {
|
|
||||||
// clear execArgv after restart, so that childProcess.fork in user code does not inherit our loader.
|
|
||||||
process.execArgv = execArgvWithoutExperimentalLoaderOptions();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!fileIsModule(configFile))
|
|
||||||
return false;
|
|
||||||
// Node.js < 20
|
|
||||||
if (!require('node:module').register) {
|
|
||||||
const innerProcess = (require('child_process') as typeof import('child_process')).fork(require.resolve('../cli'), process.argv.slice(2), {
|
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
PW_TS_ESM_LEGACY_LOADER_ON: '1',
|
|
||||||
},
|
|
||||||
execArgv: execArgvWithExperimentalLoaderOptions(),
|
|
||||||
});
|
|
||||||
|
|
||||||
innerProcess.on('close', (code: number | null) => {
|
|
||||||
if (code !== 0 && code !== null)
|
|
||||||
gracefullyProcessExitDoNotHang(code);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Nodejs >= 21
|
|
||||||
registerESMLoader();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const kTraceModes: TraceMode[] = ['on', 'off', 'on-first-retry', 'on-all-retries', 'retain-on-failure'];
|
const kTraceModes: TraceMode[] = ['on', 'off', 'on-first-retry', 'on-all-retries', 'retain-on-failure'];
|
||||||
|
|
||||||
const testOptions: [string, string][] = [
|
const testOptions: [string, string][] = [
|
||||||
@ -349,3 +344,5 @@ addTestCommand(program);
|
|||||||
addShowReportCommand(program);
|
addShowReportCommand(program);
|
||||||
addListFilesCommand(program);
|
addListFilesCommand(program);
|
||||||
addMergeReportsCommand(program);
|
addMergeReportsCommand(program);
|
||||||
|
addClearCacheCommand(program);
|
||||||
|
addFindRelatedTestsCommand(program);
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { monotonicTime } from 'playwright-core/lib/utils';
|
import { monotonicTime } from 'playwright-core/lib/utils';
|
||||||
import type { FullResult } from '../../types/testReporter';
|
import type { FullResult, TestError } from '../../types/testReporter';
|
||||||
import { webServerPluginsForConfig } from '../plugins/webServerPlugin';
|
import { webServerPluginsForConfig } from '../plugins/webServerPlugin';
|
||||||
import { collectFilesForProject, filterProjects } from './projectUtils';
|
import { collectFilesForProject, filterProjects } from './projectUtils';
|
||||||
import { createReporters } from './reporters';
|
import { createReporters } from './reporters';
|
||||||
@ -27,6 +27,8 @@ import { runWatchModeLoop } from './watchMode';
|
|||||||
import { runUIMode } from './uiMode';
|
import { runUIMode } from './uiMode';
|
||||||
import { InternalReporter } from '../reporters/internalReporter';
|
import { InternalReporter } from '../reporters/internalReporter';
|
||||||
import { Multiplexer } from '../reporters/multiplexer';
|
import { Multiplexer } from '../reporters/multiplexer';
|
||||||
|
import type { Suite } from '../common/test';
|
||||||
|
import { wrapReporterAsV2 } from '../reporters/reporterV2';
|
||||||
|
|
||||||
type ProjectConfigWithFiles = {
|
type ProjectConfigWithFiles = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -104,10 +106,15 @@ export class Runner {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadAllTests(outOfProcess?: boolean): Promise<FullResult['status']> {
|
async loadAllTests(): Promise<{ status: FullResult['status'], suite?: Suite, errors: TestError[] }> {
|
||||||
const config = this._config;
|
const config = this._config;
|
||||||
const reporter = new InternalReporter(new Multiplexer([]));
|
const errors: TestError[] = [];
|
||||||
const taskRunner = createTaskRunnerForList(config, reporter, outOfProcess ? 'out-of-process' : 'in-process', { failOnLoadErrors: true });
|
const reporter = new InternalReporter(new Multiplexer([wrapReporterAsV2({
|
||||||
|
onError(error: TestError) {
|
||||||
|
errors.push(error);
|
||||||
|
}
|
||||||
|
})]));
|
||||||
|
const taskRunner = createTaskRunnerForList(config, reporter, 'in-process', { failOnLoadErrors: true });
|
||||||
const testRun = new TestRun(config, reporter);
|
const testRun = new TestRun(config, reporter);
|
||||||
reporter.onConfigure(config.config);
|
reporter.onConfigure(config.config);
|
||||||
|
|
||||||
@ -119,7 +126,7 @@ export class Runner {
|
|||||||
if (modifiedResult && modifiedResult.status)
|
if (modifiedResult && modifiedResult.status)
|
||||||
status = modifiedResult.status;
|
status = modifiedResult.status;
|
||||||
await reporter.onExit();
|
await reporter.onExit();
|
||||||
return status;
|
return { status, suite: testRun.rootSuite, errors };
|
||||||
}
|
}
|
||||||
|
|
||||||
async watchAllTests(): Promise<FullResult['status']> {
|
async watchAllTests(): Promise<FullResult['status']> {
|
||||||
|
|||||||
@ -200,7 +200,8 @@ export function fileDependenciesForTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function collectAffectedTestFiles(dependency: string, testFileCollector: Set<string>) {
|
export function collectAffectedTestFiles(dependency: string, testFileCollector: Set<string>) {
|
||||||
testFileCollector.add(dependency);
|
if (fileDependencies.has(dependency))
|
||||||
|
testFileCollector.add(dependency);
|
||||||
for (const [testFile, deps] of fileDependencies) {
|
for (const [testFile, deps] of fileDependencies) {
|
||||||
if (deps.has(dependency))
|
if (deps.has(dependency))
|
||||||
testFileCollector.add(testFile);
|
testFileCollector.add(testFile);
|
||||||
@ -211,6 +212,13 @@ export function collectAffectedTestFiles(dependency: string, testFileCollector:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function affectedTestFiles(changes: string[]): string[] {
|
||||||
|
const result = new Set<string>();
|
||||||
|
for (const change of changes)
|
||||||
|
collectAffectedTestFiles(change, result);
|
||||||
|
return [...result];
|
||||||
|
}
|
||||||
|
|
||||||
export function internalDependenciesForTestFile(filename: string): Set<string> | undefined{
|
export function internalDependenciesForTestFile(filename: string): Set<string> | undefined{
|
||||||
return fileDependencies.get(filename);
|
return fileDependencies.get(filename);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -96,6 +96,8 @@ export class TestChildProcess {
|
|||||||
params: TestChildParams;
|
params: TestChildParams;
|
||||||
process: ChildProcess;
|
process: ChildProcess;
|
||||||
output = '';
|
output = '';
|
||||||
|
stdout = '';
|
||||||
|
stderr = '';
|
||||||
fullOutput = '';
|
fullOutput = '';
|
||||||
onOutput?: (chunk: string | Buffer) => void;
|
onOutput?: (chunk: string | Buffer) => void;
|
||||||
exited: Promise<{ exitCode: number | null, signal: string | null }>;
|
exited: Promise<{ exitCode: number | null, signal: string | null }>;
|
||||||
@ -121,8 +123,12 @@ export class TestChildProcess {
|
|||||||
process.stdout.write(`\n\nLaunching ${params.command.join(' ')}\n`);
|
process.stdout.write(`\n\nLaunching ${params.command.join(' ')}\n`);
|
||||||
this.onOutput = params.onOutput;
|
this.onOutput = params.onOutput;
|
||||||
|
|
||||||
const appendChunk = (chunk: string | Buffer) => {
|
const appendChunk = (type: 'stdout' | 'stderr', chunk: string | Buffer) => {
|
||||||
this.output += String(chunk);
|
this.output += String(chunk);
|
||||||
|
if (type === 'stderr')
|
||||||
|
this.stderr += String(chunk);
|
||||||
|
else
|
||||||
|
this.stdout += String(chunk);
|
||||||
if (process.env.PWTEST_DEBUG)
|
if (process.env.PWTEST_DEBUG)
|
||||||
process.stdout.write(String(chunk));
|
process.stdout.write(String(chunk));
|
||||||
else
|
else
|
||||||
@ -133,8 +139,8 @@ export class TestChildProcess {
|
|||||||
this._outputCallbacks.clear();
|
this._outputCallbacks.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.process.stderr!.on('data', appendChunk);
|
this.process.stderr!.on('data', appendChunk.bind(null, 'stderr'));
|
||||||
this.process.stdout!.on('data', appendChunk);
|
this.process.stdout!.on('data', appendChunk.bind(null, 'stdout'));
|
||||||
|
|
||||||
const killProcessGroup = this._killProcessTree.bind(this, 'SIGKILL');
|
const killProcessGroup = this._killProcessTree.bind(this, 'SIGKILL');
|
||||||
process.on('exit', killProcessGroup);
|
process.on('exit', killProcessGroup);
|
||||||
|
|||||||
88
tests/playwright-test/find-related-tests.spec.ts
Normal file
88
tests/playwright-test/find-related-tests.spec.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Copyright Microsoft Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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 } from './playwright-test-fixtures';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export const ctReactCliEntrypoint = path.join(__dirname, '../../packages/playwright-ct-react/cli.js');
|
||||||
|
|
||||||
|
test('should list related tests', async ({ runCLICommand }) => {
|
||||||
|
const result = await runCLICommand({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
import { defineConfig } from '@playwright/test';
|
||||||
|
export default defineConfig({});
|
||||||
|
`,
|
||||||
|
'helper.ts': `
|
||||||
|
export const value = 42;
|
||||||
|
`,
|
||||||
|
'helper2.ts': `
|
||||||
|
export { value } from './helper';
|
||||||
|
`,
|
||||||
|
'a.spec.ts': `
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
import { value } from './helper2';
|
||||||
|
if (value) {}
|
||||||
|
test('', () => {});
|
||||||
|
`,
|
||||||
|
'b.spec.ts': `
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
import { value } from './helper';
|
||||||
|
if (value) {}
|
||||||
|
test('', () => {});
|
||||||
|
`,
|
||||||
|
}, 'find-related-tests', ['helper.ts']);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.stderr).toBeFalsy();
|
||||||
|
const data = JSON.parse(result.stdout);
|
||||||
|
expect(data).toEqual({
|
||||||
|
relatedTests: [
|
||||||
|
expect.stringContaining('a.spec.ts'),
|
||||||
|
expect.stringContaining('b.spec.ts'),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should list related tests for ct', async ({ runCLICommand }) => {
|
||||||
|
const result = await runCLICommand({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
import { defineConfig } from '@playwright/experimental-ct-react';
|
||||||
|
export default defineConfig({});
|
||||||
|
`,
|
||||||
|
'playwright/index.html': `<script type="module" src="./index.js"></script>`,
|
||||||
|
'playwright/index.js': ``,
|
||||||
|
'helper.tsx': `
|
||||||
|
export const HelperButton = () => <button>Click me</button>;
|
||||||
|
`,
|
||||||
|
'button.tsx': `
|
||||||
|
import { HelperButton } from './helper';
|
||||||
|
export const Button = () => <HelperButton>Click me</HelperButton>;
|
||||||
|
`,
|
||||||
|
'button.spec.tsx': `
|
||||||
|
import { test } from '@playwright/experimental-ct-react';
|
||||||
|
import { Button } from './button';
|
||||||
|
test('foo', async ({ mount }) => {
|
||||||
|
await mount(<Button />);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, 'find-related-tests', ['helper.tsx'], ctReactCliEntrypoint);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
const data = JSON.parse(result.stdout);
|
||||||
|
expect(data).toEqual({
|
||||||
|
relatedTests: [
|
||||||
|
expect.stringContaining('button.spec.tsx'),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -16,16 +16,16 @@
|
|||||||
|
|
||||||
import { test, expect } from './playwright-test-fixtures';
|
import { test, expect } from './playwright-test-fixtures';
|
||||||
|
|
||||||
test('should list files', async ({ runListFiles }) => {
|
test('should list files', async ({ runCLICommand }) => {
|
||||||
const result = await runListFiles({
|
const result = await runCLICommand({
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
module.exports = { projects: [{ name: 'foo' }, { name: 'bar' }] };
|
module.exports = { projects: [{ name: 'foo' }, { name: 'bar' }] };
|
||||||
`,
|
`,
|
||||||
'a.test.js': ``
|
'a.test.js': ``
|
||||||
});
|
}, 'list-files');
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
|
|
||||||
const data = JSON.parse(result.output);
|
const data = JSON.parse(result.stdout);
|
||||||
expect(data).toEqual({
|
expect(data).toEqual({
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
@ -48,18 +48,18 @@ test('should list files', async ({ runListFiles }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should include testIdAttribute', async ({ runListFiles }) => {
|
test('should include testIdAttribute', async ({ runCLICommand }) => {
|
||||||
const result = await runListFiles({
|
const result = await runCLICommand({
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
module.exports = {
|
module.exports = {
|
||||||
use: { testIdAttribute: 'myid' }
|
use: { testIdAttribute: 'myid' }
|
||||||
};
|
};
|
||||||
`,
|
`,
|
||||||
'a.test.js': ``
|
'a.test.js': ``
|
||||||
});
|
}, 'list-files');
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
|
|
||||||
const data = JSON.parse(result.output);
|
const data = JSON.parse(result.stdout);
|
||||||
expect(data).toEqual({
|
expect(data).toEqual({
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
@ -76,17 +76,17 @@ test('should include testIdAttribute', async ({ runListFiles }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should report error', async ({ runListFiles }) => {
|
test('should report error', async ({ runCLICommand }) => {
|
||||||
const result = await runListFiles({
|
const result = await runCLICommand({
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
const a = 1;
|
const a = 1;
|
||||||
a = 2;
|
a = 2;
|
||||||
`,
|
`,
|
||||||
'a.test.js': ``
|
'a.test.js': ``
|
||||||
});
|
}, 'list-files');
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
|
|
||||||
const data = JSON.parse(result.output);
|
const data = JSON.parse(result.stdout);
|
||||||
expect(data).toEqual({
|
expect(data).toEqual({
|
||||||
error: {
|
error: {
|
||||||
location: {
|
location: {
|
||||||
|
|||||||
@ -37,6 +37,8 @@ type CliRunResult = {
|
|||||||
export type RunResult = {
|
export type RunResult = {
|
||||||
exitCode: number,
|
exitCode: number,
|
||||||
output: string,
|
output: string,
|
||||||
|
stdout: string,
|
||||||
|
stderr: string,
|
||||||
outputLines: string[],
|
outputLines: string[],
|
||||||
rawOutput: string,
|
rawOutput: string,
|
||||||
passed: number,
|
passed: number,
|
||||||
@ -185,19 +187,21 @@ async function runPlaywrightTest(childProcess: CommonFixtures['childProcess'], b
|
|||||||
...parsed,
|
...parsed,
|
||||||
exitCode,
|
exitCode,
|
||||||
rawOutput: output,
|
rawOutput: output,
|
||||||
|
stdout: testProcess.stdout,
|
||||||
|
stderr: testProcess.stderr,
|
||||||
report,
|
report,
|
||||||
results,
|
results,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runPlaywrightListFiles(childProcess: CommonFixtures['childProcess'], baseDir: string, env: NodeJS.ProcessEnv): Promise<{ output: string, exitCode: number }> {
|
async function runPlaywrightCLI(childProcess: CommonFixtures['childProcess'], args: string[], baseDir: string, env: NodeJS.ProcessEnv, entryPoint?: string): Promise<{ output: string, stdout: string, stderr: string, exitCode: number }> {
|
||||||
const testProcess = childProcess({
|
const testProcess = childProcess({
|
||||||
command: ['node', cliEntrypoint, 'list-files'],
|
command: ['node', entryPoint || cliEntrypoint, ...args],
|
||||||
env: cleanEnv(env),
|
env: cleanEnv(env),
|
||||||
cwd: baseDir,
|
cwd: baseDir,
|
||||||
});
|
});
|
||||||
const { exitCode } = await testProcess.exited;
|
const { exitCode } = await testProcess.exited;
|
||||||
return { exitCode, output: testProcess.output };
|
return { exitCode, output: testProcess.output, stdout: testProcess.stdout, stderr: testProcess.stderr };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cleanEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
export function cleanEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||||
@ -235,7 +239,7 @@ type Fixtures = {
|
|||||||
writeFiles: (files: Files) => Promise<string>;
|
writeFiles: (files: Files) => Promise<string>;
|
||||||
deleteFile: (file: string) => Promise<void>;
|
deleteFile: (file: string) => Promise<void>;
|
||||||
runInlineTest: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<RunResult>;
|
runInlineTest: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<RunResult>;
|
||||||
runListFiles: (files: Files) => Promise<{ output: string, exitCode: number }>;
|
runCLICommand: (files: Files, command: string, args?: string[], entryPoint?: string) => Promise<{ stdout: string, stderr: string, exitCode: number }>;
|
||||||
runWatchTest: (files: Files, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>;
|
runWatchTest: (files: Files, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>;
|
||||||
interactWithTestRunner: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>;
|
interactWithTestRunner: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>;
|
||||||
runTSC: (files: Files) => Promise<TSCResult>;
|
runTSC: (files: Files) => Promise<TSCResult>;
|
||||||
@ -268,11 +272,11 @@ export const test = base
|
|||||||
await removeFolders([cacheDir]);
|
await removeFolders([cacheDir]);
|
||||||
},
|
},
|
||||||
|
|
||||||
runListFiles: async ({ childProcess }, use, testInfo: TestInfo) => {
|
runCLICommand: async ({ childProcess }, use, testInfo: TestInfo) => {
|
||||||
const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-'));
|
const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-'));
|
||||||
await use(async (files: Files) => {
|
await use(async (files: Files, command: string, args?: string[], entryPoint?: string) => {
|
||||||
const baseDir = await writeFiles(testInfo, files, true);
|
const baseDir = await writeFiles(testInfo, files, true);
|
||||||
return await runPlaywrightListFiles(childProcess, baseDir, { PWTEST_CACHE_DIR: cacheDir });
|
return await runPlaywrightCLI(childProcess, [command, ...(args || [])], baseDir, { PWTEST_CACHE_DIR: cacheDir }, entryPoint);
|
||||||
});
|
});
|
||||||
await removeFolders([cacheDir]);
|
await removeFolders([cacheDir]);
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user