diff --git a/packages/playwright-test/src/runner/uiMode.ts b/packages/playwright-test/src/runner/uiMode.ts index bfbd0f467f..3f6b97f7dc 100644 --- a/packages/playwright-test/src/runner/uiMode.ts +++ b/packages/playwright-test/src/runner/uiMode.ts @@ -37,7 +37,8 @@ class UIMode { globalCleanup: (() => Promise) | undefined; private _globalWatcher: Watcher; private _testWatcher: Watcher; - private _originalStderr: (buffer: string | Uint8Array) => void; + private _originalStdoutWrite: NodeJS.WriteStream['write']; + private _originalStderrWrite: NodeJS.WriteStream['write']; constructor(config: FullConfigInternal) { this._config = config; @@ -57,7 +58,8 @@ class UIMode { config._internal.configCLIOverrides.use = config._internal.configCLIOverrides.use || {}; config._internal.configCLIOverrides.use.trace = { mode: 'on', sources: false }; - this._originalStderr = process.stderr.write.bind(process.stderr); + this._originalStdoutWrite = process.stdout.write; + this._originalStderrWrite = process.stderr.write; this._globalWatcher = new Watcher('deep', () => this._dispatchEvent({ method: 'listChanged' })); this._testWatcher = new Watcher('flat', events => { const collector = new Set(); @@ -111,7 +113,7 @@ class UIMode { return; } if (method === 'open' && params.location) { - open('vscode://file/' + params.location).catch(e => this._originalStderr(String(e))); + open('vscode://file/' + params.location).catch(e => this._originalStderrWrite.call(process.stderr, String(e))); return; } if (method === 'resizeTerminal') { @@ -129,6 +131,11 @@ class UIMode { await queue; }); await exitPromise; + + if (!process.env.PWTEST_DEBUG) { + process.stdout.write = this._originalStdoutWrite; + process.stderr.write = this._originalStderrWrite; + } } private async _queueListOrRun(method: string, params: any) { @@ -140,7 +147,7 @@ class UIMode { private _dispatchEvent(message: any) { // eslint-disable-next-line no-console - this._page.mainFrame().evaluateExpression(dispatchFuncSource, true, message).catch(e => this._originalStderr(String(e))); + this._page.mainFrame().evaluateExpression(dispatchFuncSource, true, message).catch(e => this._originalStderrWrite.call(process.stderr, String(e))); } private async _listTests() { diff --git a/tests/config/commonFixtures.ts b/tests/config/commonFixtures.ts index e55b51b4cd..edd464465b 100644 --- a/tests/config/commonFixtures.ts +++ b/tests/config/commonFixtures.ts @@ -81,6 +81,11 @@ export class TestChildProcess { this.exitCode = this.exited.then(r => r.exitCode); } + outputLines(): string[] { + const strippedOutput = stripAnsi(this.output); + return strippedOutput.split('\n').filter(line => line.startsWith('%%')).map(line => line.substring(2).trim()); + } + async close() { if (!this.process.killed) this._killProcessGroup('SIGINT'); diff --git a/tests/playwright-test/types-2.spec.ts b/tests/playwright-test/types-2.spec.ts index 25fa0f1e0c..1273afa110 100644 --- a/tests/playwright-test/types-2.spec.ts +++ b/tests/playwright-test/types-2.spec.ts @@ -16,6 +16,8 @@ import { test, expect } from './playwright-test-fixtures'; +test.slow(); + test('basics should work', async ({ runTSC }) => { const result = await runTSC({ 'a.spec.ts': ` diff --git a/tests/playwright-test/types.spec.ts b/tests/playwright-test/types.spec.ts index 107c1f6117..c7963ab224 100644 --- a/tests/playwright-test/types.spec.ts +++ b/tests/playwright-test/types.spec.ts @@ -16,6 +16,8 @@ import { test, expect } from './playwright-test-fixtures'; +test.slow(); + test('should check types of fixtures', async ({ runTSC }) => { const result = await runTSC({ 'helper.ts': ` diff --git a/tests/playwright-test/ui-mode-fixtures.ts b/tests/playwright-test/ui-mode-fixtures.ts index 53608647ce..37b5f39bbb 100644 --- a/tests/playwright-test/ui-mode-fixtures.ts +++ b/tests/playwright-test/ui-mode-fixtures.ts @@ -30,7 +30,7 @@ type Latch = { }; type Fixtures = { - runUITest: (files: Files, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise; + runUITest: (files: Files, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<{ page: Page, testProcess: TestChildProcess }>; createLatch: () => Latch; }; @@ -106,7 +106,7 @@ export const test = base browser = await playwright.chromium.connectOverCDP(wsEndpoint); const [context] = browser.contexts(); const [page] = context.pages(); - return page; + return { page, testProcess }; }); await browser?.close(); await testProcess?.close(); diff --git a/tests/playwright-test/ui-mode-test-filters.spec.ts b/tests/playwright-test/ui-mode-test-filters.spec.ts index 36e47663ac..206ebf626d 100644 --- a/tests/playwright-test/ui-mode-test-filters.spec.ts +++ b/tests/playwright-test/ui-mode-test-filters.spec.ts @@ -36,7 +36,7 @@ const basicTestTree = { }; test('should filter by title', async ({ runUITest }) => { - const page = await runUITest(basicTestTree); + const { page } = await runUITest(basicTestTree); await page.getByPlaceholder('Filter').fill('inner'); await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(` ▼ ◯ a.test.ts @@ -47,7 +47,7 @@ test('should filter by title', async ({ runUITest }) => { }); test('should filter by status', async ({ runUITest }) => { - const page = await runUITest(basicTestTree); + const { page } = await runUITest(basicTestTree); await page.getByTitle('Run all').click(); @@ -91,7 +91,7 @@ test('should filter by status', async ({ runUITest }) => { }); test('should filter by project', async ({ runUITest }) => { - const page = await runUITest({ + const { page } = await runUITest({ ...basicTestTree, 'playwright.config.ts': ` import { defineConfig } from '@playwright/test'; @@ -151,7 +151,7 @@ test('should filter by project', async ({ runUITest }) => { test('should not hide filtered while running', async ({ runUITest, createLatch }) => { const latch = createLatch(); - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('passes', () => {}); @@ -180,7 +180,7 @@ test('should not hide filtered while running', async ({ runUITest, createLatch } }); test('should filter skipped', async ({ runUITest, createLatch }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('passes', () => {}); diff --git a/tests/playwright-test/ui-mode-test-output.spec.ts b/tests/playwright-test/ui-mode-test-output.spec.ts index c606110950..fc5ff0ece1 100644 --- a/tests/playwright-test/ui-mode-test-output.spec.ts +++ b/tests/playwright-test/ui-mode-test-output.spec.ts @@ -19,7 +19,7 @@ import { test, expect } from './ui-mode-fixtures'; test.describe.configure({ mode: 'parallel' }); test('should print load errors', async ({ runUITest }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('syntax error', () => { @@ -32,7 +32,7 @@ test('should print load errors', async ({ runUITest }) => { }); test('should work after theme switch', async ({ runUITest, writeFiles }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('syntax error', async () => { diff --git a/tests/playwright-test/ui-mode-test-progress.spec.ts b/tests/playwright-test/ui-mode-test-progress.spec.ts index ef04fc05bb..6aa7c03018 100644 --- a/tests/playwright-test/ui-mode-test-progress.spec.ts +++ b/tests/playwright-test/ui-mode-test-progress.spec.ts @@ -32,7 +32,7 @@ test('should update trace live', async ({ runUITest, server }) => { res.end('Two'); }); - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('live test', async ({ page }) => { diff --git a/tests/playwright-test/ui-mode-test-run.spec.ts b/tests/playwright-test/ui-mode-test-run.spec.ts index 96c1d08ea1..93c7d53919 100644 --- a/tests/playwright-test/ui-mode-test-run.spec.ts +++ b/tests/playwright-test/ui-mode-test-run.spec.ts @@ -40,7 +40,7 @@ const basicTestTree = { }; test('should run visible', async ({ runUITest }) => { - const page = await runUITest(basicTestTree); + const { page } = await runUITest(basicTestTree); await expect.poll(dumpTestTree(page), { timeout: 15000 }).toContain(` ▼ ◯ a.test.ts `); @@ -64,7 +64,7 @@ test('should run visible', async ({ runUITest }) => { }); test('should show running progress', async ({ runUITest }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('test 1', async () => {}); @@ -83,7 +83,7 @@ test('should show running progress', async ({ runUITest }) => { }); test('should run on hover', async ({ runUITest }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('passes', () => {}); @@ -102,7 +102,7 @@ test('should run on hover', async ({ runUITest }) => { }); test('should run on double click', async ({ runUITest }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('passes', () => {}); @@ -120,7 +120,7 @@ test('should run on double click', async ({ runUITest }) => { }); test('should run on Enter', async ({ runUITest }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('passes', () => {}); @@ -139,7 +139,7 @@ test('should run on Enter', async ({ runUITest }) => { }); test('should run by project', async ({ runUITest }) => { - const page = await runUITest({ + const { page } = await runUITest({ ...basicTestTree, 'playwright.config.ts': ` import { defineConfig } from '@playwright/test'; @@ -218,7 +218,7 @@ test('should run by project', async ({ runUITest }) => { }); test('should stop', async ({ runUITest }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('test 0', () => { test.skip(); }); @@ -256,7 +256,7 @@ test('should stop', async ({ runUITest }) => { }); test('should run folder', async ({ runUITest }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a/folder-b/folder-c/inC.test.ts': ` import { test, expect } from '@playwright/test'; test('passes', () => {}); @@ -284,7 +284,7 @@ test('should run folder', async ({ runUITest }) => { }); test('should show time', async ({ runUITest }) => { - const page = await runUITest(basicTestTree); + const { page } = await runUITest(basicTestTree); await expect.poll(dumpTestTree(page), { timeout: 15000 }).toContain(` ▼ ◯ a.test.ts `); diff --git a/tests/playwright-test/ui-mode-test-setup.spec.ts b/tests/playwright-test/ui-mode-test-setup.spec.ts new file mode 100644 index 0000000000..52840b79b3 --- /dev/null +++ b/tests/playwright-test/ui-mode-test-setup.spec.ts @@ -0,0 +1,78 @@ +/** + * 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 { test, expect } from './ui-mode-fixtures'; + +test.describe.configure({ mode: 'parallel' }); + +test('should run global setup and teardown', async ({ runUITest }) => { + const { page, testProcess } = await runUITest({ + 'playwright.config.ts': ` + import { defineConfig } from '@playwright/test'; + export default defineConfig({ + globalSetup: './globalSetup', + globalTeardown: './globalTeardown.ts', + }); + `, + 'globalSetup.ts': ` + export default () => console.log('\\n%%from-global-setup'); + `, + 'globalTeardown.ts': ` + export default () => console.log('\\n%%from-global-teardown'); + `, + 'a.test.js': ` + import { test, expect } from '@playwright/test'; + test('should work', async ({}) => {}); + ` + }); + await page.getByTitle('Run all').click(); + await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)', { timeout: 15000 }); + await page.close(); + await expect.poll(() => testProcess.outputLines(), { timeout: 15000 }).toEqual([ + 'from-global-setup', + 'from-global-teardown', + ]); +}); + +test('should teardown on sigint', async ({ runUITest }) => { + test.skip(process.platform === 'win32', 'No sending SIGINT on Windows'); + const { page, testProcess } = await runUITest({ + 'playwright.config.ts': ` + import { defineConfig } from '@playwright/test'; + export default defineConfig({ + globalSetup: './globalSetup', + globalTeardown: './globalTeardown.ts', + }); + `, + 'globalSetup.ts': ` + export default () => console.log('\\n%%from-global-setup'); + `, + 'globalTeardown.ts': ` + export default () => console.log('\\n%%from-global-teardown'); + `, + 'a.test.js': ` + import { test, expect } from '@playwright/test'; + test('should work', async ({}) => {}); + ` + }); + await page.getByTitle('Run all').click(); + await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)', { timeout: 15000 }); + testProcess.process.kill('SIGINT'); + await expect.poll(() => testProcess.outputLines(), { timeout: 15000 }).toEqual([ + 'from-global-setup', + 'from-global-teardown', + ]); +}); diff --git a/tests/playwright-test/ui-mode-test-source.spec.ts b/tests/playwright-test/ui-mode-test-source.spec.ts index 3ddf70371e..4469db9356 100644 --- a/tests/playwright-test/ui-mode-test-source.spec.ts +++ b/tests/playwright-test/ui-mode-test-source.spec.ts @@ -31,7 +31,7 @@ const basicTestTree = { }; test('should show selected test in sources', async ({ runUITest }) => { - const page = await runUITest(basicTestTree); + const { page } = await runUITest(basicTestTree); await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(` ▼ ◯ a.test.ts ◯ first diff --git a/tests/playwright-test/ui-mode-test-tree.spec.ts b/tests/playwright-test/ui-mode-test-tree.spec.ts index 6189190dc8..9231c0beab 100644 --- a/tests/playwright-test/ui-mode-test-tree.spec.ts +++ b/tests/playwright-test/ui-mode-test-tree.spec.ts @@ -36,7 +36,7 @@ const basicTestTree = { }; test('should list tests', async ({ runUITest }) => { - const page = await runUITest(basicTestTree); + const { page } = await runUITest(basicTestTree); await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(` ▼ ◯ a.test.ts ◯ passes @@ -49,7 +49,7 @@ test('should list tests', async ({ runUITest }) => { }); test('should traverse up/down', async ({ runUITest }) => { - const page = await runUITest(basicTestTree); + const { page } = await runUITest(basicTestTree); await page.getByText('a.test.ts').click(); await expect.poll(dumpTestTree(page), { timeout: 15000 }).toContain(` ▼ ◯ a.test.ts <= @@ -83,7 +83,7 @@ test('should traverse up/down', async ({ runUITest }) => { }); test('should expand / collapse groups', async ({ runUITest }) => { - const page = await runUITest(basicTestTree); + const { page } = await runUITest(basicTestTree); await page.getByTestId('test-tree').getByText('suite').click(); await page.keyboard.press('ArrowRight'); @@ -119,7 +119,7 @@ test('should expand / collapse groups', async ({ runUITest }) => { }); test('should merge folder trees', async ({ runUITest }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a/b/c/inC.test.ts': ` import { test, expect } from '@playwright/test'; test('passes', () => {}); @@ -144,7 +144,7 @@ test('should merge folder trees', async ({ runUITest }) => { }); test('should list parametrized tests', async ({ runUITest }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test } from '@playwright/test'; test.describe('cookies', () => { @@ -173,7 +173,7 @@ test('should list parametrized tests', async ({ runUITest }) => { }); test('should update parametrized tests', async ({ runUITest, writeFiles }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test } from '@playwright/test'; test.describe('cookies', () => { diff --git a/tests/playwright-test/ui-mode-test-update.spec.ts b/tests/playwright-test/ui-mode-test-update.spec.ts index 3e3a1a53bc..9b06d93a05 100644 --- a/tests/playwright-test/ui-mode-test-update.spec.ts +++ b/tests/playwright-test/ui-mode-test-update.spec.ts @@ -36,7 +36,7 @@ const basicTestTree = { }; test('should pick new / deleted files', async ({ runUITest, writeFiles, deleteFile }) => { - const page = await runUITest(basicTestTree); + const { page } = await runUITest(basicTestTree); await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(` ▼ ◯ a.test.ts ◯ passes @@ -81,7 +81,7 @@ test('should pick new / deleted files', async ({ runUITest, writeFiles, deleteFi }); test('should pick new / deleted tests', async ({ runUITest, writeFiles, deleteFile }) => { - const page = await runUITest(basicTestTree); + const { page } = await runUITest(basicTestTree); await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(` ▼ ◯ a.test.ts ◯ passes @@ -130,7 +130,7 @@ test('should pick new / deleted tests', async ({ runUITest, writeFiles, deleteFi }); test('should pick new / deleted nested tests', async ({ runUITest, writeFiles, deleteFile }) => { - const page = await runUITest(basicTestTree); + const { page } = await runUITest(basicTestTree); await expect.poll(dumpTestTree(page), { timeout: 15000 }).toContain(` ▼ ◯ a.test.ts ◯ passes @@ -170,7 +170,7 @@ test('should pick new / deleted nested tests', async ({ runUITest, writeFiles, d }); test('should update test locations', async ({ runUITest, writeFiles, deleteFile }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('passes', () => {}); diff --git a/tests/playwright-test/ui-mode-test-watch.spec.ts b/tests/playwright-test/ui-mode-test-watch.spec.ts index 9013d76f21..1c7ff3076d 100644 --- a/tests/playwright-test/ui-mode-test-watch.spec.ts +++ b/tests/playwright-test/ui-mode-test-watch.spec.ts @@ -19,7 +19,7 @@ import { test, expect, dumpTestTree } from './ui-mode-fixtures'; test.describe.configure({ mode: 'parallel' }); test('should watch files', async ({ runUITest, writeFiles }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('passes', () => {}); @@ -59,7 +59,7 @@ test('should watch files', async ({ runUITest, writeFiles }) => { }); test('should watch e2e deps', async ({ runUITest, writeFiles }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'playwright.config.ts': ` import { defineConfig } from '@playwright/test'; export default defineConfig({ testDir: 'tests' }); @@ -94,7 +94,7 @@ test('should watch e2e deps', async ({ runUITest, writeFiles }) => { }); test('should batch watch updates', async ({ runUITest, writeFiles }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`, 'b.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`, 'c.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`, @@ -143,7 +143,7 @@ test('should batch watch updates', async ({ runUITest, writeFiles }) => { }); test('should watch all', async ({ runUITest, writeFiles }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`, 'b.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`, 'c.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`, @@ -182,7 +182,7 @@ test('should watch all', async ({ runUITest, writeFiles }) => { }); test('should watch new file', async ({ runUITest, writeFiles }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`, }); @@ -222,7 +222,7 @@ test('should watch new file', async ({ runUITest, writeFiles }) => { test('should queue watches', async ({ runUITest, writeFiles, createLatch }) => { const latch = createLatch(); - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`, 'b.test.ts': `import { test } from '@playwright/test'; test('test', async () => { ${latch.blockingCode} diff --git a/tests/playwright-test/ui-mode-trace.spec.ts b/tests/playwright-test/ui-mode-trace.spec.ts index 7bc857f8ab..042591a781 100644 --- a/tests/playwright-test/ui-mode-trace.spec.ts +++ b/tests/playwright-test/ui-mode-trace.spec.ts @@ -18,7 +18,7 @@ import { test, expect } from './ui-mode-fixtures'; test.describe.configure({ mode: 'parallel' }); test('should merge trace events', async ({ runUITest, server }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('trace test', async ({ page }) => { @@ -46,7 +46,7 @@ test('should merge trace events', async ({ runUITest, server }) => { }); test('should locate sync assertions in source', async ({ runUITest, server }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('trace test', async ({}) => { @@ -64,7 +64,7 @@ test('should locate sync assertions in source', async ({ runUITest, server }) => }); test('should show snapshots for sync assertions', async ({ runUITest, server }) => { - const page = await runUITest({ + const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('trace test', async ({ page }) => {