chore: pass the private data to the runner via config (#29467)

This commit is contained in:
Pavel Feldman 2024-02-13 09:34:03 -08:00 committed by GitHub
parent 8fca4c86aa
commit 7aef5249d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 203 additions and 197 deletions

View file

@ -7,4 +7,4 @@
!types/** !types/**
!index.d.ts !index.d.ts
!index.js !index.js
!plugin.js

View file

@ -16,16 +16,26 @@
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, findRelatedTestsCommand } = require('./lib/cliOverrides');
const { createPlugin } = require('./lib/vitePlugin');
const defineConfig = (config, ...configs) => originalDefineConfig({ const defineConfig = (...configs) => {
...config, const original = originalDefineConfig(...configs);
build: { return {
...config.build, ...original,
babelPlugins: [ '@playwright/test': {
[require.resolve('./lib/tsxTransform')] ...original['@playwright/test'],
], plugins: [() => createPlugin()],
} babelPlugins: [
}, ...configs); [require.resolve('./lib/tsxTransform')]
],
cli: {
'clear-cache': clearCacheCommand,
'find-related-tests': findRelatedTestsCommand,
},
}
};
};
const test = baseTest.extend(fixtures); const test = baseTest.extend(fixtures);

View file

@ -21,7 +21,6 @@
}, },
"./lib/mount": "./lib/mount.js", "./lib/mount": "./lib/mount.js",
"./lib/program": "./lib/program.js", "./lib/program": "./lib/program.js",
"./plugin": "./plugin.js",
"./types/component": { "./types/component": {
"types": "./types/component.d.ts" "types": "./types/component.d.ts"
} }

View file

@ -1,17 +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.
*/
module.exports = require('./lib/vitePlugin');

View file

@ -0,0 +1,35 @@
/**
* 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 { removeFolder } from 'playwright/lib/program';
import { affectedTestFiles, cacheDir } from 'playwright/lib/transform/compilationCache';
import { buildBundle } from './vitePlugin';
import { resolveDirs } from './viteUtils';
import type { Suite } from 'playwright/lib/common/test';
import type { FullConfig } from 'playwright/test';
export async function clearCacheCommand(config: FullConfig, configDir: string) {
const dirs = await resolveDirs(configDir, config);
if (dirs)
await removeFolder(dirs.outDir);
await removeFolder(cacheDir);
}
export async function findRelatedTestsCommand(files: string[], config: FullConfig, configDir: string, suite: Suite) {
await buildBundle(config, configDir, suite);
return { relatedTests: affectedTestFiles(files) };
}

View file

@ -21,14 +21,15 @@ 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, frameworkConfig } from './viteUtils';
import type { ComponentRegistry } from './viteUtils'; import type { ComponentRegistry } from './viteUtils';
export async function runDevServer(configFile: string, registerSourceFile: string, frameworkPluginFactory: () => Promise<any>) { export async function runDevServer(configFile: string) {
const config = await loadConfigFromFile(configFile); const config = await loadConfigFromFile(configFile);
if (!config) if (!config)
return; return;
const { registerSourceFile, frameworkPluginFactory } = frameworkConfig(config.config);
const runner = new Runner(config); const runner = new Runner(config);
await runner.loadAllTests(); await runner.loadAllTests();
const componentRegistry: ComponentRegistry = new Map(); const componentRegistry: ComponentRegistry = new Map();

View file

@ -16,56 +16,17 @@
import type { Command } from 'playwright-core/lib/utilsBundle'; import type { Command } from 'playwright-core/lib/utilsBundle';
import path from 'path'; import { program } from 'playwright/lib/program';
import { program, removeFolder, setClearCacheCommandOverride, setFindRelatedTestsCommandOverride, withRunnerAndMutedWrite } from 'playwright/lib/program';
import { runDevServer } from './devServer'; import { runDevServer } from './devServer';
import { resolveDirs } from './viteUtils';
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 _framework: { registerSource: string, frameworkPluginFactory: () => Promise<any> };
export function initializePlugin(framework: { registerSource: string, frameworkPluginFactory: () => Promise<any> }) {
_framework = framework;
}
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, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`); 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, _framework.registerSource, _framework.frameworkPluginFactory); runDevServer(options.config);
}); });
} }
setFindRelatedTestsCommandOverride(async (files, options) => {
await withRunnerAndMutedWrite(options.config, async (runner, config, configDir) => {
const result = await runner.loadAllTests();
if (result.status !== 'passed' || !result.suite)
return { errors: result.errors };
await buildBundle({
config,
configDir,
suite: result.suite,
registerSourceFile: _framework.registerSource,
frameworkPluginFactory: _framework.frameworkPluginFactory,
});
const resolvedFiles = (files as string[]).map(file => path.resolve(process.cwd(), file));
return { relatedTests: affectedTestFiles(resolvedFiles) };
});
});
setClearCacheCommandOverride(async options => {
const configFile = options.config;
const config = await loadConfigFromFile(configFile);
if (!config)
return;
const dirs = await resolveDirs(config.configDir, config.config);
if (dirs)
await removeFolder(dirs.outDir);
await removeFolder(cacheDir);
});
addDevServerCommand(program); addDevServerCommand(program);

View file

@ -30,7 +30,7 @@ import type { TestRunnerPlugin } from '../../playwright/src/plugins';
import { source as injectedSource } from './generated/indexSource'; import { source as injectedSource } from './generated/indexSource';
import type { ImportInfo } from './tsxTransform'; import type { ImportInfo } from './tsxTransform';
import type { ComponentRegistry } from './viteUtils'; import type { ComponentRegistry } from './viteUtils';
import { createConfig, hasJSComponents, populateComponentsFromTests, resolveDirs, resolveEndpoint, transformIndexFile } from './viteUtils'; import { createConfig, frameworkConfig, hasJSComponents, populateComponentsFromTests, resolveDirs, resolveEndpoint, transformIndexFile } from './viteUtils';
import { resolveHook } from 'playwright/lib/transform/transform'; import { resolveHook } from 'playwright/lib/transform/transform';
const log = debug('pw:vite'); const log = debug('pw:vite');
@ -38,9 +38,7 @@ const log = debug('pw:vite');
let stoppableServer: any; let stoppableServer: any;
const playwrightVersion = getPlaywrightVersion(); const playwrightVersion = getPlaywrightVersion();
export function createPlugin( export function createPlugin(): TestRunnerPlugin {
registerSourceFile: string,
frameworkPluginFactory?: () => Promise<Plugin>): TestRunnerPlugin {
let configDir: string; let configDir: string;
let config: FullConfig; let config: FullConfig;
return { return {
@ -52,13 +50,7 @@ export function createPlugin(
}, },
begin: async (suite: Suite) => { begin: async (suite: Suite) => {
const result = await buildBundle({ const result = await buildBundle(config, configDir, suite);
config,
configDir,
suite,
registerSourceFile,
frameworkPluginFactory: frameworkPluginFactory,
});
if (!result) if (!result)
return; return;
@ -96,16 +88,11 @@ type BuildInfo = {
} }
}; };
export async function buildBundle(options: { export async function buildBundle(config: FullConfig, configDir: string, suite: Suite): Promise<{ buildInfo: BuildInfo, viteConfig: Record<string, any> } | null> {
config: FullConfig, const { registerSourceFile, frameworkPluginFactory } = frameworkConfig(config);
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. // Detect a running dev server and use it if available.
const endpoint = resolveEndpoint(options.config); const endpoint = resolveEndpoint(config);
const protocol = endpoint.https ? 'https:' : 'http:'; const protocol = endpoint.https ? 'https:' : 'http:';
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)) {
@ -116,7 +103,7 @@ export async function buildBundle(options: {
} }
} }
const dirs = await resolveDirs(options.configDir, options.config); const dirs = await resolveDirs(configDir, config);
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.`);
@ -128,7 +115,7 @@ export async function buildBundle(options: {
let buildExists = false; let buildExists = false;
let buildInfo: BuildInfo; let buildInfo: BuildInfo;
const registerSource = injectedSource + '\n' + await fs.promises.readFile(options.registerSourceFile, 'utf-8'); const registerSource = injectedSource + '\n' + await fs.promises.readFile(registerSourceFile, 'utf-8');
const registerSourceHash = calculateSha1(registerSource); const registerSourceHash = calculateSha1(registerSource);
const { version: viteVersion, build, mergeConfig } = await import('vite'); const { version: viteVersion, build, mergeConfig } = await import('vite');
@ -168,7 +155,7 @@ export async function buildBundle(options: {
buildInfo.components = [...componentRegistry.values()]; buildInfo.components = [...componentRegistry.values()];
const jsxInJS = hasJSComponents(buildInfo.components); const jsxInJS = hasJSComponents(buildInfo.components);
const viteConfig = await createConfig(dirs, options.config, options.frameworkPluginFactory, jsxInJS); const viteConfig = await createConfig(dirs, config, frameworkPluginFactory, jsxInJS);
if (sourcesDirty) { if (sourcesDirty) {
// Only add out own plugin when we actually build / transform. // Only add out own plugin when we actually build / transform.
@ -183,7 +170,7 @@ export async function buildBundle(options: {
{ {
// Update dependencies based on the vite build. // Update dependencies based on the vite build.
for (const projectSuite of options.suite.suites) { for (const projectSuite of suite.suites) {
for (const fileSuite of projectSuite.suites) { for (const fileSuite of projectSuite.suites) {
// For every test file... // For every test file...
const testFile = fileSuite.location!.file; const testFile = fileSuite.location!.file;

View file

@ -192,3 +192,7 @@ export function transformIndexFile(id: string, content: string, templateDir: str
map: { mappings: '' } map: { mappings: '' }
}; };
} }
export function frameworkConfig(config: FullConfig): { registerSourceFile: string, frameworkPluginFactory?: () => Promise<Plugin> } {
return (config as any)['@playwright/experimental-ct-core'];
}

View file

@ -15,8 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program'); const { program } = require('@playwright/experimental-ct-core/lib/program');
const { _framework } = require('./index');
initializePlugin(_framework);
program.parse(process.argv); program.parse(process.argv);

View file

@ -17,14 +17,17 @@
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 defineConfig = (config, ...configs) => {
const frameworkPluginFactory = () => import('@vitejs/plugin-react').then(plugin => plugin.default()); return originalDefineConfig({
...config,
const plugin = () => { '@playwright/test': {
// Only fetch upon request to avoid resolution in workers. packageJSON: require.resolve('./package.json'),
const { createPlugin } = require('@playwright/experimental-ct-core/plugin'); },
return createPlugin(registerSource, frameworkPluginFactory); '@playwright/experimental-ct-core': {
registerSourceFile: path.join(__dirname, 'registerSource.mjs'),
frameworkPluginFactory: () => import('@vitejs/plugin-react').then(plugin => plugin.default()),
},
}, ...configs);
}; };
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } }; module.exports = { test, expect, devices, defineConfig };

View file

@ -17,14 +17,17 @@
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 defineConfig = (config, ...configs) => {
const frameworkPluginFactory = () => import('@vitejs/plugin-react').then(plugin => plugin.default()); return originalDefineConfig({
...config,
const plugin = () => { '@playwright/test': {
// Only fetch upon request to avoid resolution in workers. packageJSON: require.resolve('./package.json'),
const { createPlugin } = require('@playwright/experimental-ct-core/plugin'); },
return createPlugin(registerSource, frameworkPluginFactory); '@playwright/experimental-ct-core': {
registerSourceFile: path.join(__dirname, 'registerSource.mjs'),
frameworkPluginFactory: () => import('@vitejs/plugin-react').then(plugin => plugin.default()),
},
}, ...configs);
}; };
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } }; module.exports = { test, expect, devices, defineConfig };

View file

@ -17,14 +17,17 @@
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 defineConfig = (config, ...configs) => {
const frameworkPluginFactory = () => import('vite-plugin-solid').then(plugin => plugin.default()); return originalDefineConfig({
...config,
const plugin = () => { '@playwright/test': {
// Only fetch upon request to avoid resolution in workers. packageJSON: require.resolve('./package.json'),
const { createPlugin } = require('@playwright/experimental-ct-core/plugin'); },
return createPlugin(registerSource, frameworkPluginFactory); '@playwright/experimental-ct-core': {
registerSourceFile: path.join(__dirname, 'registerSource.mjs'),
frameworkPluginFactory: () => import('vite-plugin-solid').then(plugin => plugin.default()),
},
}, ...configs);
}; };
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } }; module.exports = { test, expect, devices, defineConfig };

View file

@ -17,14 +17,17 @@
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 defineConfig = (config, ...configs) => {
const frameworkPluginFactory = () => import('@sveltejs/vite-plugin-svelte').then(plugin => plugin.svelte()); return originalDefineConfig({
...config,
const plugin = () => { '@playwright/test': {
// Only fetch upon request to avoid resolution in workers. packageJSON: require.resolve('./package.json'),
const { createPlugin } = require('@playwright/experimental-ct-core/plugin'); },
return createPlugin(registerSource, frameworkPluginFactory); '@playwright/experimental-ct-core': {
registerSourceFile: path.join(__dirname, 'registerSource.mjs'),
frameworkPluginFactory: () => import('@sveltejs/vite-plugin-svelte').then(plugin => plugin.svelte()),
},
}, ...configs);
}; };
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } }; module.exports = { test, expect, devices, defineConfig };

View file

@ -17,14 +17,17 @@
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 defineConfig = (config, ...configs) => {
const frameworkPluginFactory = () => import('@vitejs/plugin-vue').then(plugin => plugin.default()); return originalDefineConfig({
...config,
const plugin = () => { '@playwright/test': {
// Only fetch upon request to avoid resolution in workers. packageJSON: require.resolve('./package.json'),
const { createPlugin } = require('@playwright/experimental-ct-core/plugin'); },
return createPlugin(registerSource, frameworkPluginFactory); '@playwright/experimental-ct-core': {
registerSourceFile: path.join(__dirname, 'registerSource.mjs'),
frameworkPluginFactory: () => import('@vitejs/plugin-vue').then(plugin => plugin.default()),
},
}, ...configs);
}; };
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } }; module.exports = { test, expect, devices, defineConfig };

View file

@ -17,14 +17,17 @@
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 defineConfig = (config, ...configs) => {
const frameworkPluginFactory = () => import('@vitejs/plugin-vue2').then(plugin => plugin.default()); return originalDefineConfig({
...config,
const plugin = () => { '@playwright/test': {
// Only fetch upon request to avoid resolution in workers. packageJSON: require.resolve('./package.json'),
const { createPlugin } = require('@playwright/experimental-ct-core/plugin'); },
return createPlugin(registerSource, frameworkPluginFactory); '@playwright/experimental-ct-core': {
registerSourceFile: path.join(__dirname, 'registerSource.mjs'),
frameworkPluginFactory: () => import('@vitejs/plugin-vue2').then(plugin => plugin.default()),
},
}, ...configs);
}; };
const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs);
module.exports = { test, expect, devices, defineConfig, _framework: { registerSource, frameworkPluginFactory } }; module.exports = { test, expect, devices, defineConfig };

View file

@ -71,7 +71,8 @@ export class FullConfigInternal {
this.configCLIOverrides = configCLIOverrides; this.configCLIOverrides = configCLIOverrides;
this.globalOutputDir = takeFirst(configCLIOverrides.outputDir, pathResolve(configDir, config.outputDir), throwawayArtifactsPath, path.resolve(process.cwd())); this.globalOutputDir = takeFirst(configCLIOverrides.outputDir, pathResolve(configDir, config.outputDir), throwawayArtifactsPath, path.resolve(process.cwd()));
this.ignoreSnapshots = takeFirst(configCLIOverrides.ignoreSnapshots, config.ignoreSnapshots, false); this.ignoreSnapshots = takeFirst(configCLIOverrides.ignoreSnapshots, config.ignoreSnapshots, false);
this.plugins = ((config as any)._plugins || []).map((p: any) => ({ factory: p })); const privateConfiguration = (config as any)['@playwright/test'];
this.plugins = (privateConfiguration?.plugins || []).map((p: any) => ({ factory: p }));
this.config = { this.config = {
configFile, configFile,
@ -96,6 +97,11 @@ export class FullConfigInternal {
workers: 0, workers: 0,
webServer: null, webServer: null,
}; };
for (const key in config) {
if (key.startsWith('@'))
(this.config as any)[key] = (config as any)[key];
}
(this.config as any)[configInternalSymbol] = this; (this.config as any)[configInternalSymbol] = this;
const workers = takeFirst(configCLIOverrides.workers, config.workers, '50%'); const workers = takeFirst(configCLIOverrides.workers, config.workers, '50%');
@ -127,7 +133,7 @@ export class FullConfigInternal {
resolveProjectDependencies(this.projects); resolveProjectDependencies(this.projects);
this._assignUniqueProjectIds(this.projects); this._assignUniqueProjectIds(this.projects);
setTransformConfig({ setTransformConfig({
babelPlugins: (config as any).build?.babelPlugins || [], babelPlugins: privateConfiguration?.babelPlugins || [],
external: config.build?.external || [], external: config.build?.external || [],
}); });
this.config.projects = this.projects.map(p => p.project); this.config.projects = this.projects.map(p => p.project);

View file

@ -66,19 +66,7 @@ function addListFilesCommand(program: Command) {
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`); command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
command.option('--project <project-name...>', `Only run tests from the specified list of projects (default: list all projects)`); command.option('--project <project-name...>', `Only run tests from the specified list of projects (default: list all projects)`);
command.option('--project-grep <pattern>', `Only run tests from the projects matching this regular expression (default: list all projects)`); command.option('--project-grep <pattern>', `Only run tests from the projects matching this regular expression (default: list all projects)`);
command.action(async (args, opts) => { command.action(async (args, opts) => listTestFiles(opts));
try {
await listTestFiles(opts);
} catch (e) {
console.error(e);
gracefullyProcessExitDoNotHang(1);
}
});
}
let clearCacheCommandOverride: (opts: any) => Promise<void>;
export function setClearCacheCommandOverride(body: (opts: any) => Promise<void>) {
clearCacheCommandOverride = body;
} }
function addClearCacheCommand(program: Command) { function addClearCacheCommand(program: Command) {
@ -86,8 +74,15 @@ function addClearCacheCommand(program: Command) {
command.description('clears build and test caches'); 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.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
command.action(async opts => { command.action(async opts => {
if (clearCacheCommandOverride) const configInternal = await loadConfigFromFile(opts.config);
return clearCacheCommandOverride(opts); if (!configInternal)
return;
const { config, configDir } = configInternal;
const override = (config as any)['@playwright/test']?.['cli']?.['clear-cache'];
if (override) {
await override(config, configDir);
return;
}
await removeFolder(cacheDir); await removeFolder(cacheDir);
}); });
} }
@ -102,23 +97,20 @@ export async function removeFolder(folder: string) {
} }
} }
let findRelatedTestsCommandOverride: (files: string[], opts: any) => Promise<void>;
export function setFindRelatedTestsCommandOverride(body: (files: string[], opts: any) => Promise<void>) {
findRelatedTestsCommandOverride = body;
}
function addFindRelatedTestsCommand(program: Command) { function addFindRelatedTestsCommand(program: Command) {
const command = program.command('find-related-tests [source-files...]'); const command = program.command('find-related-tests [source-files...]');
command.description('Returns the list of related tests to the given 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.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
command.action(async (files, options) => { command.action(async (files, options) => {
if (findRelatedTestsCommandOverride) await withRunnerAndMutedWrite(options.config, async (runner, config, configDir) => {
return findRelatedTestsCommandOverride(files, options);
await withRunnerAndMutedWrite(options.config, async runner => {
const result = await runner.loadAllTests(); const result = await runner.loadAllTests();
if (result.status !== 'passed' || !result.suite) if (result.status !== 'passed' || !result.suite)
return { errors: result.errors }; return { errors: result.errors };
const resolvedFiles = (files as string[]).map(file => path.resolve(process.cwd(), file)); const resolvedFiles = (files as string[]).map(file => path.resolve(process.cwd(), file));
const override = (config as any)['@playwright/test']?.['cli']?.['find-related-tests'];
if (override)
return await override(resolvedFiles, config, configDir, result.suite);
return { relatedTests: affectedTestFiles(resolvedFiles) }; return { relatedTests: affectedTestFiles(resolvedFiles) };
}); });
}); });
@ -217,7 +209,10 @@ export async function withRunnerAndMutedWrite(configFile: string | undefined, ca
async function listTestFiles(opts: { [key: string]: any }) { async function listTestFiles(opts: { [key: string]: any }) {
if (opts.project && opts.projectGrep) if (opts.project && opts.projectGrep)
throw new Error('Only one of --project and --project-grep can be specified.'); throw new Error('Only one of --project and --project-grep can be specified.');
await withRunnerAndMutedWrite(opts.config, async runner => runner.listTestFiles(opts.project, opts.projectGrep)); await withRunnerAndMutedWrite(opts.config, async (runner, config) => {
const frameworkPackage = (config as any)['@playwright/test']?.['packageJSON'];
return await runner.listTestFiles(frameworkPackage, opts.project, opts.projectGrep);
});
} }
async function mergeReports(reportDir: string | undefined, opts: { [key: string]: any }) { async function mergeReports(reportDir: string | undefined, opts: { [key: string]: any }) {

View file

@ -15,6 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import path from 'path';
import { monotonicTime } from 'playwright-core/lib/utils'; import { monotonicTime } from 'playwright-core/lib/utils';
import type { FullResult, TestError } from '../../types/testReporter'; import type { FullResult, TestError } from '../../types/testReporter';
import { webServerPluginsForConfig } from '../plugins/webServerPlugin'; import { webServerPluginsForConfig } from '../plugins/webServerPlugin';
@ -39,6 +40,8 @@ type ProjectConfigWithFiles = {
type ConfigListFilesReport = { type ConfigListFilesReport = {
projects: ProjectConfigWithFiles[]; projects: ProjectConfigWithFiles[];
cliEntryPoint?: string;
error?: TestError;
}; };
export class Runner { export class Runner {
@ -48,10 +51,11 @@ export class Runner {
this._config = config; this._config = config;
} }
async listTestFiles(projectNames: string[] | undefined, projectGrep: string | undefined): Promise<any> { async listTestFiles(frameworkPackage: string | undefined, projectNames: string[] | undefined, projectGrep: string | undefined): Promise<ConfigListFilesReport> {
const projects = filterProjects(this._config.projects, projectNames, projectGrep); const projects = filterProjects(this._config.projects, projectNames, projectGrep);
const report: ConfigListFilesReport = { const report: ConfigListFilesReport = {
projects: [] projects: [],
cliEntryPoint: frameworkPackage ? path.join(path.dirname(frameworkPackage), 'cli.js') : undefined,
}; };
for (const project of projects) { for (const project of projects) {
report.projects.push({ report.projects.push({

View file

@ -656,8 +656,12 @@ test('should merge ct configs', async ({ runInlineTest }) => {
expect(derivedConfig).toEqual(expect.objectContaining({ expect(derivedConfig).toEqual(expect.objectContaining({
use: { foo: 1, bar: 2 }, use: { foo: 1, bar: 2 },
grep: 'hi', grep: 'hi',
build: { babelPlugins: [expect.anything()] }, '@playwright/test': expect.objectContaining({
_plugins: [expect.anything()], babelPlugins: [[expect.stringContaining('tsxTransform.js')]]
}),
'@playwright/experimental-ct-core': expect.objectContaining({
registerSourceFile: expect.stringContaining('registerSource'),
}),
})); }));
`, `,
'a.test.ts': ` 'a.test.ts': `

View file

@ -324,14 +324,14 @@ test('globalSetup auth should compile', async ({ runTSC }) => {
test('teardown order', async ({ runInlineTest }) => { test('teardown order', async ({ runInlineTest }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
const _plugins = []; const plugins = [];
for (let i = 1; i < 4; ++i) { for (let i = 1; i < 4; ++i) {
_plugins.push(() => ({ plugins.push(() => ({
setup: () => console.log('\\n%%setup ' + i), setup: () => console.log('\\n%%setup ' + i),
teardown: () => console.log('\\n%%teardown ' + i), teardown: () => console.log('\\n%%teardown ' + i),
})); }));
} }
export default { _plugins }; export default { '@playwright/test': { plugins } };
`, `,
'a.test.ts': ` 'a.test.ts': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
@ -353,9 +353,9 @@ test('teardown order', async ({ runInlineTest }) => {
test('teardown after error', async ({ runInlineTest }) => { test('teardown after error', async ({ runInlineTest }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
const _plugins = []; const plugins = [];
for (let i = 1; i < 4; ++i) { for (let i = 1; i < 4; ++i) {
_plugins.push(() => ({ plugins.push(() => ({
setup: () => console.log('\\n%%setup ' + i), setup: () => console.log('\\n%%setup ' + i),
teardown: () => { teardown: () => {
console.log('\\n%%teardown ' + i); console.log('\\n%%teardown ' + i);
@ -363,7 +363,7 @@ test('teardown after error', async ({ runInlineTest }) => {
}, },
})); }));
} }
export default { _plugins }; export default { '@playwright/test': { plugins } };
`, `,
'a.test.ts': ` 'a.test.ts': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';

View file

@ -883,7 +883,8 @@ for (const useIntermediateMergeReport of [false, true] as const) {
'playwright.config.ts': ` 'playwright.config.ts': `
import { gitCommitInfo } from 'playwright/lib/plugins'; import { gitCommitInfo } from 'playwright/lib/plugins';
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
export default { _plugins: [gitCommitInfo()] }; const plugins = [gitCommitInfo()];
export default { '@playwright/test': { plugins } };
`, `,
'example.spec.ts': ` 'example.spec.ts': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
@ -945,7 +946,7 @@ for (const useIntermediateMergeReport of [false, true] as const) {
'revision.email': 'shakespeare@example.local', 'revision.email': 'shakespeare@example.local',
}, },
}); });
export default { _plugins: [plugin] }; export default { '@playwright/test': { plugins: [plugin] } };
`, `,
'example.spec.ts': ` 'example.spec.ts': `
import { gitCommitInfo } from 'playwright/lib/plugins'; import { gitCommitInfo } from 'playwright/lib/plugins';

View file

@ -450,8 +450,8 @@ test('sigint should stop plugins', async ({ interactWithTestRunner }) => {
const testProcess = await interactWithTestRunner({ const testProcess = await interactWithTestRunner({
'playwright.config.ts': ` 'playwright.config.ts': `
const _plugins = []; const plugins = [];
_plugins.push(() => ({ plugins.push(() => ({
setup: async () => { setup: async () => {
console.log('Plugin1 setup'); console.log('Plugin1 setup');
console.log('%%SEND-SIGINT%%'); console.log('%%SEND-SIGINT%%');
@ -462,7 +462,7 @@ test('sigint should stop plugins', async ({ interactWithTestRunner }) => {
} }
})); }));
_plugins.push(() => ({ plugins.push(() => ({
setup: async () => { setup: async () => {
console.log('Plugin2 setup'); console.log('Plugin2 setup');
}, },
@ -471,7 +471,7 @@ test('sigint should stop plugins', async ({ interactWithTestRunner }) => {
} }
})); }));
module.exports = { module.exports = {
_plugins '@playwright/test': { plugins }
}; };
`, `,
'a.spec.js': ` 'a.spec.js': `
@ -500,8 +500,8 @@ test('sigint should stop plugins 2', async ({ interactWithTestRunner }) => {
const testProcess = await interactWithTestRunner({ const testProcess = await interactWithTestRunner({
'playwright.config.ts': ` 'playwright.config.ts': `
const _plugins = []; const plugins = [];
_plugins.push(() => ({ plugins.push(() => ({
setup: async () => { setup: async () => {
console.log('Plugin1 setup'); console.log('Plugin1 setup');
}, },
@ -510,7 +510,7 @@ test('sigint should stop plugins 2', async ({ interactWithTestRunner }) => {
} }
})); }));
_plugins.push(() => ({ plugins.push(() => ({
setup: async () => { setup: async () => {
console.log('Plugin2 setup'); console.log('Plugin2 setup');
console.log('%%SEND-SIGINT%%'); console.log('%%SEND-SIGINT%%');
@ -520,7 +520,7 @@ test('sigint should stop plugins 2', async ({ interactWithTestRunner }) => {
console.log('Plugin2 teardown'); console.log('Plugin2 teardown');
} }
})); }));
module.exports = { _plugins }; module.exports = { '@playwright/test': { plugins } };
`, `,
'a.spec.js': ` 'a.spec.js': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';