diff --git a/packages/playwright-ct-core/index.js b/packages/playwright-ct-core/index.js index e11dd84ace..1a4f185a31 100644 --- a/packages/playwright-ct-core/index.js +++ b/packages/playwright-ct-core/index.js @@ -16,7 +16,6 @@ const { test: baseTest, expect, devices, defineConfig: originalDefineConfig } = require('playwright/test'); const { fixtures } = require('./lib/mount'); -const { clearCacheCommand } = require('./lib/cliOverrides'); const { createPlugin } = require('./lib/vitePlugin'); const defineConfig = (...configs) => { @@ -29,9 +28,6 @@ const defineConfig = (...configs) => { babelPlugins: [ [require.resolve('./lib/tsxTransform')] ], - cli: { - 'clear-cache': clearCacheCommand, - }, } }; }; diff --git a/packages/playwright-ct-core/src/cliOverrides.ts b/packages/playwright-ct-core/src/cliOverrides.ts deleted file mode 100644 index 3173ce7f9d..0000000000 --- a/packages/playwright-ct-core/src/cliOverrides.ts +++ /dev/null @@ -1,28 +0,0 @@ - -/** - * 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 { cacheDir } from 'playwright/lib/transform/compilationCache'; -import { resolveDirs } from './viteUtils'; -import type { FullConfigInternal } from 'playwright/lib/common/config'; -import { removeFolderAndLogToConsole } from 'playwright/lib/runner/testServer'; - -export async function clearCacheCommand(config: FullConfigInternal) { - const dirs = await resolveDirs(config.configDir, config.config); - if (dirs) - await removeFolderAndLogToConsole(dirs.outDir); - await removeFolderAndLogToConsole(cacheDir); -} diff --git a/packages/playwright-ct-core/src/vitePlugin.ts b/packages/playwright-ct-core/src/vitePlugin.ts index f954fcd2b2..462730c61c 100644 --- a/packages/playwright-ct-core/src/vitePlugin.ts +++ b/packages/playwright-ct-core/src/vitePlugin.ts @@ -32,6 +32,7 @@ import type { ComponentRegistry } from './viteUtils'; import { createConfig, frameworkConfig, hasJSComponents, populateComponentsFromTests, resolveDirs, resolveEndpoint, transformIndexFile } from './viteUtils'; import { resolveHook } from 'playwright/lib/transform/transform'; import { runDevServer } from './devServer'; +import { removeDirAndLogToConsole } from 'playwright/lib/util'; const log = debug('pw:vite'); @@ -78,6 +79,13 @@ export function createPlugin(): TestRunnerPlugin { startDevServer: async () => { return await runDevServer(config); }, + + clearCache: async () => { + const configDir = config.configFile ? path.dirname(config.configFile) : config.rootDir; + const dirs = await resolveDirs(configDir, config); + if (dirs) + await removeDirAndLogToConsole(dirs.outDir); + }, }; } diff --git a/packages/playwright/package.json b/packages/playwright/package.json index 519c33171c..16c3bd24c7 100644 --- a/packages/playwright/package.json +++ b/packages/playwright/package.json @@ -24,8 +24,6 @@ "./lib/program": "./lib/program.js", "./lib/transform/babelBundle": "./lib/transform/babelBundle.js", "./lib/transform/compilationCache": "./lib/transform/compilationCache.js", - "./lib/runner/runner": "./lib/runner/runner.js", - "./lib/runner/testServer": "./lib/runner/testServer.js", "./lib/transform/esmLoader": "./lib/transform/esmLoader.js", "./lib/transform/transform": "./lib/transform/transform.js", "./lib/internalsForTest": "./lib/internalsForTest.js", diff --git a/packages/playwright/src/plugins/index.ts b/packages/playwright/src/plugins/index.ts index c3b6f52bf4..2f7995cb2f 100644 --- a/packages/playwright/src/plugins/index.ts +++ b/packages/playwright/src/plugins/index.ts @@ -22,6 +22,7 @@ export interface TestRunnerPlugin { setup?(config: FullConfig, configDir: string, reporter: ReporterV2): Promise; populateDependencies?(): Promise; startDevServer?(): Promise<() => Promise>; + clearCache?(): Promise; begin?(suite: Suite): Promise; end?(): Promise; teardown?(): Promise; diff --git a/packages/playwright/src/program.ts b/packages/playwright/src/program.ts index 349915c47b..e57c0a3328 100644 --- a/packages/playwright/src/program.ts +++ b/packages/playwright/src/program.ts @@ -34,7 +34,6 @@ export { program } from 'playwright-core/lib/cli/program'; import type { ReporterDescription } from '../types/test'; import { prepareErrorStack } from './reporters/base'; import * as testServer from './runner/testServer'; -import { clearCacheAndLogToConsole } from './runner/testServer'; import { runWatchModeLoop } from './runner/watchMode'; function addTestCommand(program: Command) { @@ -74,10 +73,13 @@ function addClearCacheCommand(program: Command) { command.description('clears build and test caches'); command.option('-c, --config ', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`); command.action(async opts => { - const configInternal = await loadConfigFromFileRestartIfNeeded(opts.config); - if (!configInternal) + const config = await loadConfigFromFileRestartIfNeeded(opts.config); + if (!config) return; - await clearCacheAndLogToConsole(configInternal); + const runner = new Runner(config); + const { status } = await runner.clearCache(); + const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); + gracefullyProcessExitDoNotHang(exitCode); }); } diff --git a/packages/playwright/src/runner/runner.ts b/packages/playwright/src/runner/runner.ts index a58276ac26..2744fd2730 100644 --- a/packages/playwright/src/runner/runner.ts +++ b/packages/playwright/src/runner/runner.ts @@ -22,7 +22,7 @@ import type { FullResult, TestError } from '../../types/testReporter'; import { webServerPluginsForConfig } from '../plugins/webServerPlugin'; import { collectFilesForProject, filterProjects } from './projectUtils'; import { createErrorCollectingReporter, createReporters } from './reporters'; -import { TestRun, createTaskRunner, createTaskRunnerForDevServer, createTaskRunnerForList, createTaskRunnerForRelatedTestFiles } from './tasks'; +import { TestRun, createTaskRunner, createTaskRunnerForClearCache, createTaskRunnerForDevServer, createTaskRunnerForList, createTaskRunnerForRelatedTestFiles } from './tasks'; import type { FullConfigInternal } from '../common/config'; import { affectedTestFiles } from '../transform/compilationCache'; import { InternalReporter } from '../reporters/internalReporter'; @@ -131,6 +131,17 @@ export class Runner { await reporter.onExit(); return { status }; } + + async clearCache() { + const reporter = new InternalReporter([createErrorCollectingReporter(true)]); + const taskRunner = createTaskRunnerForClearCache(this._config, reporter, 'in-process', true); + const testRun = new TestRun(this._config); + reporter.onConfigure(this._config.config); + const status = await taskRunner.run(testRun, 0); + await reporter.onEnd({ status }); + await reporter.onExit(); + return { status }; + } } export type LastRunInfo = { diff --git a/packages/playwright/src/runner/tasks.ts b/packages/playwright/src/runner/tasks.ts index 8f94a8d19a..6b8c9f7464 100644 --- a/packages/playwright/src/runner/tasks.ts +++ b/packages/playwright/src/runner/tasks.ts @@ -26,12 +26,13 @@ import type { Task } from './taskRunner'; import { TaskRunner } from './taskRunner'; import type { FullConfigInternal, FullProjectInternal } from '../common/config'; import { collectProjectsAndTestFiles, createRootSuite, loadFileSuites, loadGlobalHook } from './loadUtils'; -import type { Matcher } from '../util'; +import { removeDirAndLogToConsole, type Matcher } from '../util'; import { Suite } from '../common/test'; import { buildDependentProjects, buildTeardownToSetupsMap, filterProjects } from './projectUtils'; import { FailureTracker } from './failureTracker'; import { detectChangedTestFiles } from './vcs'; import type { InternalReporter } from '../reporters/internalReporter'; +import { cacheDir } from '../transform/compilationCache'; const readDirAsync = promisify(fs.readdir); @@ -139,6 +140,22 @@ export function createTaskRunnerForRelatedTestFiles(config: FullConfigInternal, return taskRunner; } +export function createTaskRunnerForClearCache(config: FullConfigInternal, reporter: InternalReporter, mode: 'in-process' | 'out-of-process', setupPlugins: boolean): TaskRunner { + const taskRunner = TaskRunner.create(reporter, config.config.globalTimeout); + if (setupPlugins) { + for (const plugin of config.plugins) + taskRunner.addTask('plugin setup', createPluginSetupTask(plugin)); + } + taskRunner.addTask('clear cache', { + setup: async () => { + await removeDirAndLogToConsole(cacheDir); + for (const plugin of config.plugins) + await plugin.instance?.clearCache?.(); + }, + }); + return taskRunner; +} + function createReportBeginTask(): Task { return { setup: async (reporter, { rootSuite }) => { diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index cd6b7ca0ce..e6522bad46 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -23,7 +23,7 @@ import type * as reporterTypes from '../../types/testReporter'; import { affectedTestFiles, collectAffectedTestFiles, dependenciesForTestFile } from '../transform/compilationCache'; import type { ConfigLocation, FullConfigInternal } from '../common/config'; import { createErrorCollectingReporter, createReporterForTestServer, createReporters } from './reporters'; -import { TestRun, createTaskRunnerForList, createTaskRunnerForTestServer, createTaskRunnerForWatchSetup, createTaskRunnerForListFiles, createTaskRunnerForDevServer, createTaskRunnerForRelatedTestFiles } from './tasks'; +import { TestRun, createTaskRunnerForList, createTaskRunnerForTestServer, createTaskRunnerForWatchSetup, createTaskRunnerForListFiles, createTaskRunnerForDevServer, createTaskRunnerForRelatedTestFiles, createTaskRunnerForClearCache } from './tasks'; import { open } from 'playwright-core/lib/utilsBundle'; import ListReporter from '../reporters/list'; import { SigIntWatcher } from './sigIntWatcher'; @@ -35,7 +35,6 @@ import { webServerPluginsForConfig } from '../plugins/webServerPlugin'; import type { TraceViewerRedirectOptions, TraceViewerServerOptions } from 'playwright-core/lib/server/trace/viewer/traceViewer'; import type { TestRunnerPluginRegistration } from '../plugins'; import { serializeError } from '../util'; -import { cacheDir } from '../transform/compilationCache'; import { baseFullConfig } from '../isomorphic/teleReceiver'; import { InternalReporter } from '../reporters/internalReporter'; import type { ReporterV2 } from '../reporters/reporterV2'; @@ -202,9 +201,17 @@ export class TestServerDispatcher implements TestServerInterface { } async clearCache(params: Parameters[0]): ReturnType { - const { config } = await this._loadConfig(); - if (config) - await clearCacheAndLogToConsole(config); + const reporter = new InternalReporter([]); + const config = await this._loadConfigOrReportError(reporter); + if (!config) + return; + + const taskRunner = createTaskRunnerForClearCache(config, reporter, 'out-of-process', false); + const testRun = new TestRun(config); + reporter.onConfigure(config.config); + const status = await taskRunner.run(testRun, 0); + await reporter.onEnd({ status }); + await reporter.onExit(); } async listFiles(params: Parameters[0]): ReturnType { @@ -525,23 +532,3 @@ export async function resolveCtDirs(config: FullConfigInternal) { templateDir }; } - -export async function clearCacheAndLogToConsole(config: FullConfigInternal) { - const override = (config.config as any)['@playwright/test']?.['cli']?.['clear-cache']; - if (override) { - await override(config); - return; - } - await removeFolderAndLogToConsole(cacheDir); -} - -export async function removeFolderAndLogToConsole(folder: string) { - try { - if (!fs.existsSync(folder)) - return; - // eslint-disable-next-line no-console - console.log(`Removing ${await fs.promises.realpath(folder)}`); - await fs.promises.rm(folder, { recursive: true, force: true }); - } catch { - } -} diff --git a/packages/playwright/src/util.ts b/packages/playwright/src/util.ts index 367bb721d9..06e18f206a 100644 --- a/packages/playwright/src/util.ts +++ b/packages/playwright/src/util.ts @@ -348,3 +348,14 @@ function fileExists(resolved: string) { function dirExists(resolved: string) { return fs.statSync(resolved, { throwIfNoEntry: false })?.isDirectory(); } + +export async function removeDirAndLogToConsole(dir: string) { + try { + if (!fs.existsSync(dir)) + return; + // eslint-disable-next-line no-console + console.log(`Removing ${await fs.promises.realpath(dir)}`); + await fs.promises.rm(dir, { recursive: true, force: true }); + } catch { + } +} diff --git a/tests/playwright-test/clear-cache.spec.ts b/tests/playwright-test/clear-cache.spec.ts index ffd0ec3cf4..128896c2e3 100644 --- a/tests/playwright-test/clear-cache.spec.ts +++ b/tests/playwright-test/clear-cache.spec.ts @@ -15,9 +15,6 @@ */ 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 clear cache with type:module', async ({ runCLICommand }) => { const result = await runCLICommand({ @@ -46,6 +43,6 @@ test('should clear cache for ct', async ({ runCLICommand }) => { import { test } from '@playwright/test'; test('example', () => {}); `, - }, 'clear-cache', [], ctReactCliEntrypoint); + }, 'clear-cache', []); expect(result.exitCode).toBe(0); }); diff --git a/tests/playwright-test/playwright-test-fixtures.ts b/tests/playwright-test/playwright-test-fixtures.ts index f9670fa305..23c26a3e3c 100644 --- a/tests/playwright-test/playwright-test-fixtures.ts +++ b/tests/playwright-test/playwright-test-fixtures.ts @@ -203,9 +203,9 @@ async function runPlaywrightTest(childProcess: CommonFixtures['childProcess'], b }; } -async function runPlaywrightCLI(childProcess: CommonFixtures['childProcess'], args: string[], baseDir: string, env: NodeJS.ProcessEnv, entryPoint?: string): Promise<{ output: string, stdout: string, stderr: string, exitCode: number }> { +async function runPlaywrightCLI(childProcess: CommonFixtures['childProcess'], args: string[], baseDir: string, env: NodeJS.ProcessEnv): Promise<{ output: string, stdout: string, stderr: string, exitCode: number }> { const testProcess = childProcess({ - command: ['node', entryPoint || cliEntrypoint, ...args], + command: ['node', cliEntrypoint, ...args], env: cleanEnv(env), cwd: baseDir, }); @@ -249,7 +249,7 @@ type Fixtures = { writeFiles: (files: Files) => Promise; deleteFile: (file: string) => Promise; runInlineTest: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise; - runCLICommand: (files: Files, command: string, args?: string[], entryPoint?: string) => Promise<{ stdout: string, stderr: string, exitCode: number }>; + runCLICommand: (files: Files, command: string, args?: string[]) => Promise<{ stdout: string, stderr: string, exitCode: number }>; startCLICommand: (files: Files, command: string, args?: string[], options?: RunOptions) => Promise; runWatchTest: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise; interactWithTestRunner: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise; @@ -285,9 +285,9 @@ export const test = base runCLICommand: async ({ childProcess }, use, testInfo: TestInfo) => { const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-')); - await use(async (files: Files, command: string, args?: string[], entryPoint?: string) => { + await use(async (files: Files, command: string, args?: string[]) => { const baseDir = await writeFiles(testInfo, files, true); - return await runPlaywrightCLI(childProcess, [command, ...(args || [])], baseDir, { PWTEST_CACHE_DIR: cacheDir }, entryPoint); + return await runPlaywrightCLI(childProcess, [command, ...(args || [])], baseDir, { PWTEST_CACHE_DIR: cacheDir }); }); await removeFolders([cacheDir]); }, diff --git a/tests/playwright-test/test-server.spec.ts b/tests/playwright-test/test-server.spec.ts index 26b393538f..6f03b2c804 100644 --- a/tests/playwright-test/test-server.spec.ts +++ b/tests/playwright-test/test-server.spec.ts @@ -193,3 +193,12 @@ test('find related test files', async ({ startTestServer, writeFiles }) => { expect((await testServerConnection.runGlobalTeardown({})).status).toBe('passed'); }); + +test('clear cache', async ({ startTestServer, writeFiles }) => { + await writeFiles(ctFiles); + const testServerConnection = await startTestServer(); + await testServerConnection.initialize({ interceptStdio: true }); + expect((await testServerConnection.runGlobalSetup({})).status).toBe('passed'); + await testServerConnection.clearCache({}); + expect((await testServerConnection.runGlobalTeardown({})).status).toBe('passed'); +});