From 49c1f9eb025d02f8bfc38351592f3466fe8bc242 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Sat, 15 Jul 2023 15:11:31 -0700 Subject: [PATCH] feat(ui): run deps in UI mode if dep projects are checked (#24245) --- .../playwright-test/src/runner/loadUtils.ts | 8 +- packages/playwright-test/src/runner/tasks.ts | 6 +- packages/playwright-test/src/runner/uiMode.ts | 9 +- packages/trace-viewer/src/ui/uiModeView.tsx | 4 +- .../ui-mode-test-setup.spec.ts | 99 ++++++++++++++++++- utils/limits.sh | 0 6 files changed, 111 insertions(+), 15 deletions(-) mode change 100644 => 100755 utils/limits.sh diff --git a/packages/playwright-test/src/runner/loadUtils.ts b/packages/playwright-test/src/runner/loadUtils.ts index ebf52a63f2..62ef1877e0 100644 --- a/packages/playwright-test/src/runner/loadUtils.ts +++ b/packages/playwright-test/src/runner/loadUtils.ts @@ -32,7 +32,7 @@ import { dependenciesForTestFile } from '../transform/compilationCache'; import { sourceMapSupport } from '../utilsBundle'; import type { RawSourceMap } from 'source-map'; -export async function collectProjectsAndTestFiles(testRun: TestRun, additionalFileMatcher: Matcher | undefined) { +export async function collectProjectsAndTestFiles(testRun: TestRun, doNotRunTestsOutsideProjectFilter: boolean, additionalFileMatcher: Matcher | undefined) { const config = testRun.config; const fsCache = new Map(); const sourceMapCache = new Map(); @@ -40,7 +40,8 @@ export async function collectProjectsAndTestFiles(testRun: TestRun, additionalFi // First collect all files for the projects in the command line, don't apply any file filters. const allFilesForProject = new Map(); - for (const project of filterProjects(config.projects, config.cliProjectFilter)) { + const filteredProjects = filterProjects(config.projects, config.cliProjectFilter); + for (const project of filteredProjects) { const files = await collectFilesForProject(project, fsCache); allFilesForProject.set(project, files); } @@ -68,7 +69,8 @@ export async function collectProjectsAndTestFiles(testRun: TestRun, additionalFi for (const [project, type] of projectClosure) { if (type === 'dependency') { filesToRunByProject.delete(project); - const files = allFilesForProject.get(project) || await collectFilesForProject(project, fsCache); + const treatProjectAsEmpty = doNotRunTestsOutsideProjectFilter && !filteredProjects.includes(project); + const files = treatProjectAsEmpty ? [] : allFilesForProject.get(project) || await collectFilesForProject(project, fsCache); filesToRunByProject.set(project, files); } } diff --git a/packages/playwright-test/src/runner/tasks.ts b/packages/playwright-test/src/runner/tasks.ts index cfb48b3238..40c27b0182 100644 --- a/packages/playwright-test/src/runner/tasks.ts +++ b/packages/playwright-test/src/runner/tasks.ts @@ -76,7 +76,7 @@ export function createTaskRunnerForWatchSetup(config: FullConfigInternal, report export function createTaskRunnerForWatch(config: FullConfigInternal, reporter: ReporterV2, additionalFileMatcher?: Matcher): TaskRunner { const taskRunner = new TaskRunner(reporter, 0); - taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, additionalFileMatcher })); + taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, doNotRunTestsOutsideProjectFilter: true, additionalFileMatcher })); addRunTasks(taskRunner, config); return taskRunner; } @@ -171,9 +171,9 @@ function createRemoveOutputDirsTask(): Task { }; } -function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, failOnLoadErrors: boolean, additionalFileMatcher?: Matcher }): Task { +function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filterOnly: boolean, failOnLoadErrors: boolean, doNotRunTestsOutsideProjectFilter?: boolean, additionalFileMatcher?: Matcher }): Task { return async (testRun, errors, softErrors) => { - await collectProjectsAndTestFiles(testRun, options.additionalFileMatcher); + await collectProjectsAndTestFiles(testRun, !!options.doNotRunTestsOutsideProjectFilter, options.additionalFileMatcher); await loadFileSuites(testRun, mode, options.failOnLoadErrors ? errors : softErrors); testRun.rootSuite = await createRootSuite(testRun, options.failOnLoadErrors ? errors : softErrors, !!options.filterOnly); // Fail when no tests. diff --git a/packages/playwright-test/src/runner/uiMode.ts b/packages/playwright-test/src/runner/uiMode.ts index 79923728bd..d231ede2a1 100644 --- a/packages/playwright-test/src/runner/uiMode.ts +++ b/packages/playwright-test/src/runner/uiMode.ts @@ -45,10 +45,6 @@ class UIMode { process.env.PW_LIVE_TRACE_STACKS = '1'; config.cliListOnly = false; config.cliPassWithNoTests = true; - for (const project of config.projects) { - project.deps = []; - project.teardown = undefined; - } for (const p of config.projects) { p.project.retries = 0; @@ -153,7 +149,7 @@ class UIMode { if (method === 'list') await this._listTests(); if (method === 'run') - await this._runTests(params.testIds); + await this._runTests(params.testIds, params.projects); } private _dispatchEvent(method: string, params?: any) { @@ -178,11 +174,12 @@ class UIMode { this._globalWatcher.update([...projectDirs], false); } - private async _runTests(testIds: string[]) { + private async _runTests(testIds: string[], projects: string[]) { await this._stopTests(); const testIdSet = testIds ? new Set(testIds) : null; this._config.cliListOnly = false; + this._config.cliProjectFilter = projects.length ? projects : undefined; this._config.testIdMatcher = id => !testIdSet || testIdSet.has(id); const reporters = await createReporters(this._config, 'ui'); diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 30ee7e80e6..ddc6ae1656 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -153,7 +153,7 @@ export const UIModeView: React.FC<{}> = ({ setProgress({ total: testIds.size, passed: 0, failed: 0, skipped: 0 }); setRunningState({ testIds }); - await sendMessage('run', { testIds: [...testIds] }); + await sendMessage('run', { testIds: [...testIds], projects: [...projectFilters].filter(([_, v]) => v).map(([p]) => p) }); // Clear pending tests in case of interrupt. for (const test of testModel.rootSuite?.allTests() || []) { if (test.results[0]?.duration === -1) @@ -162,7 +162,7 @@ export const UIModeView: React.FC<{}> = ({ setTestModel({ ...testModel }); setRunningState(undefined); }); - }, [runningState, testModel]); + }, [projectFilters, runningState, testModel]); const isRunningTest = !!runningState; diff --git a/tests/playwright-test/ui-mode-test-setup.spec.ts b/tests/playwright-test/ui-mode-test-setup.spec.ts index 58f143f50f..62c323247d 100644 --- a/tests/playwright-test/ui-mode-test-setup.spec.ts +++ b/tests/playwright-test/ui-mode-test-setup.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { test, expect, retries } from './ui-mode-fixtures'; +import { test, expect, retries, dumpTestTree } from './ui-mode-fixtures'; test.describe.configure({ mode: 'parallel', retries }); @@ -76,3 +76,100 @@ test('should teardown on sigint', async ({ runUITest }) => { 'from-global-teardown', ]); }); + +const testsWithSetup = { + 'playwright.config.ts': ` + import { defineConfig } from '@playwright/test'; + export default defineConfig({ + projects: [ + { name: 'setup', teardown: 'teardown', testMatch: 'setup.ts' }, + { name: 'test', testMatch: 'test.ts', dependencies: ['setup'] }, + { name: 'teardown', testMatch: 'teardown.ts' }, + ] + }); + `, + 'setup.ts': ` + import { test, expect } from '@playwright/test'; + test('setup', async ({}) => { + console.log('from-setup'); + }); + `, + 'test.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async ({}) => { + console.log('from-test'); + }); + `, + 'teardown.ts': ` + import { test, expect } from '@playwright/test'; + test('teardown', async ({}) => { + console.log('from-teardown'); + }); + `, +}; + +test('should run setup and teardown projects (1)', async ({ runUITest }) => { + const { page } = await runUITest(testsWithSetup); + await page.getByText('Status:').click(); + await page.getByLabel('setup').setChecked(false); + await page.getByLabel('teardown').setChecked(false); + await page.getByLabel('test').setChecked(false); + + await page.getByTitle('Run all').click(); + + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ✅ setup.ts + ✅ setup + ▼ ✅ teardown.ts + ✅ teardown + ▼ ✅ test.ts + ✅ test + `); + + await page.getByTitle('Toggle output').click(); + await expect(page.getByTestId('output')).toContainText(`from-setup`); + await expect(page.getByTestId('output')).toContainText(`from-test`); + await expect(page.getByTestId('output')).toContainText(`from-teardown`); +}); + +test('should run setup and teardown projects (2)', async ({ runUITest }) => { + const { page } = await runUITest(testsWithSetup); + await page.getByText('Status:').click(); + await page.getByLabel('setup').setChecked(false); + await page.getByLabel('teardown').setChecked(true); + await page.getByLabel('test').setChecked(true); + + await page.getByTitle('Run all').click(); + + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ✅ teardown.ts + ✅ teardown + ▼ ✅ test.ts + ✅ test + `); + + await page.getByTitle('Toggle output').click(); + await expect(page.getByTestId('output')).toContainText(`from-test`); + await expect(page.getByTestId('output')).toContainText(`from-teardown`); + await expect(page.getByTestId('output')).not.toContainText(`from-setup`); +}); + +test('should run setup and teardown projects (3)', async ({ runUITest }) => { + const { page } = await runUITest(testsWithSetup); + await page.getByText('Status:').click(); + await page.getByLabel('setup').setChecked(false); + await page.getByLabel('teardown').setChecked(false); + await page.getByLabel('test').setChecked(true); + + await page.getByTitle('Run all').click(); + + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ✅ test.ts + ✅ test + `); + + await page.getByTitle('Toggle output').click(); + await expect(page.getByTestId('output')).toContainText(`from-test`); + await expect(page.getByTestId('output')).not.toContainText(`from-setup`); + await expect(page.getByTestId('output')).not.toContainText(`from-teardown`); +}); diff --git a/utils/limits.sh b/utils/limits.sh old mode 100644 new mode 100755