From 76373da62e1bb384a4195f439a743f033e8a118b Mon Sep 17 00:00:00 2001 From: Rui Figueira Date: Fri, 26 Jul 2024 11:33:24 +0100 Subject: [PATCH] test(trace-viewer): spawn trace viewer tests --- src/extension.ts | 4 + src/traceViewer.ts | 21 +++- src/vscodeTypes.ts | 4 +- tests/mock/vscode.ts | 1 + tests/spawn-trace-viewer.spec.ts | 184 +++++++++++++++++++++++++++++++ tests/utils.ts | 14 ++- 6 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 tests/spawn-trace-viewer.spec.ts diff --git a/src/extension.ts b/src/extension.ts index e95ff6a81..ab4a254a5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -759,6 +759,10 @@ export class Extension implements RunHooks { this._treeItemSelected(testItem); } + traceViewerInfoForTest() { + return this._traceViewer()?.infoForTest(); + } + private _showTrace(testItem: vscodeTypes.TestItem) { const traceUrl = (testItem as any)[traceUrlSymbol]; if (traceUrl) diff --git a/src/traceViewer.ts b/src/traceViewer.ts index 0599dd099..a34f897aa 100644 --- a/src/traceViewer.ts +++ b/src/traceViewer.ts @@ -27,6 +27,7 @@ export class SpawnTraceViewer { private _traceViewerProcess: ChildProcess | undefined; private _currentFile?: string; private _config: TestConfig; + private _serverUrlPrefix?: string; constructor(vscode: vscodeTypes.VSCode, envProvider: () => NodeJS.ProcessEnv, config: TestConfig) { this._vscode = vscode; @@ -82,6 +83,13 @@ export class SpawnTraceViewer { this._vscode.window.showErrorMessage(error.message); this.close(); }); + if (this._vscode.isUnderTest) { + traceViewerProcess.stdout?.on('data', data => { + const match = data.toString().match(/Listening on (.*)/); + if (match) + this._serverUrlPrefix = match[1]; + }); + } } checkVersion() { @@ -96,14 +104,25 @@ export class SpawnTraceViewer { return true; } - close() { this._traceViewerProcess?.stdin?.end(); this._traceViewerProcess = undefined; this._currentFile = undefined; + this._serverUrlPrefix = undefined; } dispose() { this.close(); } + + infoForTest() { + if (!this._serverUrlPrefix) + return; + return { + type: 'spawn', + serverUrlPrefix: this._serverUrlPrefix, + testConfigFile: this._config.configFile, + traceFile: this.currentFile(), + }; + } } diff --git a/src/vscodeTypes.ts b/src/vscodeTypes.ts index 96be58b27..0fce597bf 100644 --- a/src/vscodeTypes.ts +++ b/src/vscodeTypes.ts @@ -57,4 +57,6 @@ export type { TerminalLink, } from 'vscode'; -export type VSCode = typeof import('vscode'); +export type VSCode = typeof import('vscode') & { + isUnderTest?: boolean; +}; diff --git a/tests/mock/vscode.ts b/tests/mock/vscode.ts index de29c62ad..4527eba1d 100644 --- a/tests/mock/vscode.ts +++ b/tests/mock/vscode.ts @@ -498,6 +498,7 @@ export class TestController { private _didCreateTestRun = new EventEmitter(); readonly onDidCreateTestRun = this._didCreateTestRun.event; + refreshHandler: (item: TestItem | null) => Promise; resolveHandler: (item: TestItem | null) => Promise; constructor(readonly vscode: VSCode, id: string, label: string) { diff --git a/tests/spawn-trace-viewer.spec.ts b/tests/spawn-trace-viewer.spec.ts new file mode 100644 index 000000000..e2c9b9ef3 --- /dev/null +++ b/tests/spawn-trace-viewer.spec.ts @@ -0,0 +1,184 @@ +/** + * 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 { enableConfigs, expect, selectConfig, selectTestItem, test, traceViewerInfo } from './utils'; + +test.beforeEach(({ showBrowser }) => { + test.skip(showBrowser); + // prevents spawn trace viewer process from opening in browser + process.env.PWTEST_UNDER_TEST = '1'; +}); + +test.use({ showTrace: true, envRemoteName: 'ssh-remote' }); + +test('@smoke should open trace viewer', async ({ activate }) => { + const { vscode, testController } = await activate({ + 'playwright.config.js': `module.exports = { testDir: 'tests' }`, + 'tests/test.spec.ts': ` + import { test } from '@playwright/test'; + test('should pass', async () => {}); + `, + }); + + await testController.run(); + await testController.expandTestItems(/test.spec/); + selectTestItem(testController.findTestItems(/pass/)[0]); + + await expect.poll(() => traceViewerInfo(vscode)).toMatchObject({ + type: 'spawn', + traceFile: expect.stringContaining('pass'), + }); +}); + +test('should change opened file in trace viewer', async ({ activate }) => { + const { vscode, testController } = await activate({ + 'playwright.config.js': `module.exports = { testDir: 'tests' }`, + 'tests/test.spec.ts': ` + import { test } from '@playwright/test'; + test('one', async () => {}); + test('two', async () => {}); + `, + }); + + await testController.run(); + await testController.expandTestItems(/test.spec/); + + selectTestItem(testController.findTestItems(/one/)[0]); + + await expect.poll(() => traceViewerInfo(vscode)).toMatchObject({ + type: 'spawn', + traceFile: expect.stringContaining('one'), + }); + + selectTestItem(testController.findTestItems(/two/)[0]); + + await expect.poll(() => traceViewerInfo(vscode)).toMatchObject({ + type: 'spawn', + traceFile: expect.stringContaining('two'), + }); +}); + +test('should not open trace viewer if test did not run', async ({ activate }) => { + const { vscode, testController } = await activate({ + 'playwright.config.js': `module.exports = { testDir: 'tests' }`, + 'tests/test.spec.ts': ` + import { test } from '@playwright/test'; + test('should pass', async () => {}); + `, + }); + + await testController.expandTestItems(/test.spec/); + selectTestItem(testController.findTestItems(/pass/)[0]); + + await expect.poll(() => traceViewerInfo(vscode)).toBeUndefined(); +}); + +test('should refresh trace viewer while test is running', async ({ activate }) => { + const { vscode, testController } = await activate({ + 'playwright.config.js': `module.exports = { testDir: 'tests' }`, + 'tests/test.spec.ts': ` + import { test } from '@playwright/test'; + test('should pass', async () => await new Promise(r => setTimeout(r, 1000))); + `, + }); + + await testController.expandTestItems(/test.spec/); + selectTestItem(testController.findTestItems(/pass/)[0]); + + await Promise.all([ + testController.run(), + expect.poll(() => traceViewerInfo(vscode)).toMatchObject({ + type: 'spawn', + traceFile: expect.stringMatching(/\.json$/), + }), + ]); + + await expect.poll(() => traceViewerInfo(vscode)).toMatchObject({ + type: 'spawn', + traceFile: expect.stringMatching(/\.zip$/), + }); +}); + +test('should close trace viewer if test configs refreshed', async ({ activate }) => { + const { vscode, testController } = await activate({ + 'playwright.config.js': `module.exports = { testDir: 'tests' }`, + 'tests/test.spec.ts': ` + import { test } from '@playwright/test'; + test('should pass', async () => {}); + `, + }); + + await testController.run(); + await testController.expandTestItems(/test.spec/); + selectTestItem(testController.findTestItems(/pass/)[0]); + + await expect.poll(() => traceViewerInfo(vscode)).toMatchObject({ + type: 'spawn', + traceFile: expect.stringContaining('pass'), + }); + + await testController.refreshHandler(null); + + await expect.poll(() => traceViewerInfo(vscode)).toBeUndefined(); +}); + +test('should open new trace viewer when another test config is selected', async ({ activate }) => { + const { vscode, testController } = await activate({ + 'playwright1.config.js': `module.exports = { testDir: 'tests1' }`, + 'playwright2.config.js': `module.exports = { testDir: 'tests2' }`, + 'tests1/test.spec.ts': ` + import { test } from '@playwright/test'; + test('one', () => {}); + `, + 'tests2/test.spec.ts': ` + import { test } from '@playwright/test'; + test('one', () => {}); + `, + }); + + await enableConfigs(vscode, ['playwright1.config.js', 'playwright2.config.js']); + await selectConfig(vscode, 'playwright1.config.js'); + + await testController.expandTestItems(/test.spec/); + const testItems = testController.findTestItems(/one/); + await testController.run(testItems); + + selectTestItem(testItems[0]); + + await expect.poll(() => traceViewerInfo(vscode)).toMatchObject({ + type: 'spawn', + serverUrlPrefix: expect.stringContaining('http'), + testConfigFile: expect.stringContaining('playwright1.config.js'), + }); + const serverUrlPrefix1 = traceViewerInfo(vscode); + + // closes opened trace viewer + await selectConfig(vscode, 'playwright2.config.js'); + + await expect.poll(() => traceViewerInfo(vscode)).toBeUndefined(); + + // opens trace viewer from selected test config + selectTestItem(testItems[0]); + + await expect.poll(() => traceViewerInfo(vscode)).toMatchObject({ + type: 'spawn', + serverUrlPrefix: expect.stringContaining('http'), + testConfigFile: expect.stringContaining('playwright2.config.js'), + }); + const serverUrlPrefix2 = traceViewerInfo(vscode); + + expect(serverUrlPrefix2).not.toBe(serverUrlPrefix1); +}); diff --git a/tests/utils.ts b/tests/utils.ts index b2e845dca..e1b871422 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -29,6 +29,8 @@ type ActivateResult = { type TestFixtures = { vscode: VSCode, activate: (files: { [key: string]: string }, options?: { rootDir?: string, workspaceFolders?: [string, any][], env?: Record }) => Promise; + showTrace: boolean; + envRemoteName?: string; }; export type WorkerOptions = { @@ -114,12 +116,14 @@ export const test = baseTest.extend({ overridePlaywrightVersion: [undefined, { option: true, scope: 'worker' }], showBrowser: [false, { option: true, scope: 'worker' }], vsCodeVersion: [1.86, { option: true, scope: 'worker' }], + showTrace: false, + envRemoteName: undefined, vscode: async ({ browser, vsCodeVersion }, use) => { await use(new VSCode(vsCodeVersion, path.resolve(__dirname, '..'), browser)); }, - activate: async ({ vscode, showBrowser, overridePlaywrightVersion }, use, testInfo) => { + activate: async ({ vscode, showBrowser, showTrace, envRemoteName, overridePlaywrightVersion }, use, testInfo) => { const instances: VSCode[] = []; await use(async (files: { [key: string]: string }, options?: { rootDir?: string, workspaceFolders?: [string, any][], env?: Record }) => { if (options?.workspaceFolders) { @@ -134,6 +138,10 @@ export const test = baseTest.extend({ configuration.update('env', options.env); if (showBrowser) configuration.update('reuseBrowser', true); + if (showTrace) + configuration.update('showTrace', true); + if (envRemoteName) + vscode.env.remoteName = envRemoteName; const extension = new Extension(vscode, vscode.context); if (overridePlaywrightVersion) @@ -222,3 +230,7 @@ export async function singleWebViewByPanelType(vscode: VSCode, viewType: string) await expect.poll(() => vscode.webViewsByPanelType(viewType)).toHaveLength(1); return vscode.webViewsByPanelType(viewType)[0]; } + +export function traceViewerInfo(vscode: VSCode): { type: 'spawn' | 'embedded', serverUrlPrefix?: string, testConfigFile: string } | undefined { + return vscode.extensions[0].traceViewerInfoForTest(); +}