diff --git a/tests-integration/tests/baseTest.ts b/tests-integration/tests/baseTest.ts index 7bdf0a346..e557dae5c 100644 --- a/tests-integration/tests/baseTest.ts +++ b/tests-integration/tests/baseTest.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { test as base, type Page, _electron } from '@playwright/test'; +import { test as base, type Page, _electron, Locator, FrameLocator } from '@playwright/test'; import { downloadAndUnzipVSCode } from '@vscode/test-electron/out/download'; export { expect } from '@playwright/test'; import path from 'path'; @@ -23,21 +23,33 @@ import { spawnSync } from 'child_process'; export type TestOptions = { vscodeVersion: string; + playwrightVersion?: 'next' | 'beta'; }; type TestFixtures = TestOptions & { workbox: Page, + getWebview: (overlappingElem: Locator) => Promise, createProject: () => Promise, createTempDir: () => Promise, }; export const test = base.extend({ vscodeVersion: ['insiders', { option: true }], - workbox: async ({ vscodeVersion, createProject, createTempDir }, use) => { + playwrightVersion: [undefined, { option: true }], + workbox: async ({ vscodeVersion, createProject, createTempDir, }, use) => { + // remove all VSCODE_* environment variables, otherwise it fails to load custom webviews with the following error: + // InvalidStateError: Failed to register a ServiceWorker: The document is in an invalid state + const env = { ...process.env } as Record; + for (const prop in env) { + if (/VSCODE_/i.test(prop)) + delete env[prop]; + } + const defaultCachePath = await createTempDir(); const vscodePath = await downloadAndUnzipVSCode(vscodeVersion); const electronApp = await _electron.launch({ executablePath: vscodePath, + env, args: [ // Stolen from https://github.com/microsoft/vscode-test/blob/0ec222ef170e102244569064a12898fb203e5bb7/lib/runTest.ts#L126-L160 // https://github.com/microsoft/vscode/issues/84238 @@ -68,7 +80,22 @@ export const test = base.extend({ await fs.promises.cp(logPath, logOutputPath, { recursive: true }); } }, - createProject: async ({ createTempDir }, use) => { + getWebview: async ({ workbox }, use) => { + await use(async overlappingLocator => { + const webviewId = await overlappingLocator.evaluate(overlappingElem => { + function overlaps(elem: Element) { + const rect1 = elem.getBoundingClientRect(); + const rect2 = overlappingElem.getBoundingClientRect(); + return rect1.right >= rect2.left && rect1.left <= rect2.right && rect1.bottom >= rect2.top && rect1.top <= rect2.bottom; + } + return [...document.querySelectorAll('.webview')].find(overlaps)?.getAttribute('name'); + }); + if (!webviewId) + throw new Error(`No webview found overlapping ${overlappingLocator}`); + return workbox.frameLocator(`[name='${webviewId}']`).frameLocator('iframe'); + }); + }, + createProject: async ({ createTempDir, playwrightVersion }, use) => { await use(async () => { // We want to be outside of the project directory to avoid already installed dependencies. const projectPath = await createTempDir(); @@ -76,7 +103,7 @@ export const test = base.extend({ await fs.promises.rm(projectPath, { recursive: true }); console.log(`Creating project in ${projectPath}`); await fs.promises.mkdir(projectPath); - spawnSync(`npm init playwright@latest --yes -- --quiet --browser=chromium --gha --install-deps`, { + spawnSync(`npm init playwright@latest --yes -- --quiet --browser=chromium --gha --install-deps ${playwrightVersion ? `--${playwrightVersion}` : ''}`, { cwd: projectPath, stdio: 'inherit', shell: true, diff --git a/tests-integration/tests/trace-viewer.test.ts b/tests-integration/tests/trace-viewer.test.ts new file mode 100644 index 000000000..15b594909 --- /dev/null +++ b/tests-integration/tests/trace-viewer.test.ts @@ -0,0 +1,50 @@ +/** + * 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 './baseTest'; + +test.use({ playwrightVersion: 'next' }); + +test('should show tracer when test runs', async ({ workbox, getWebview }) => { + await workbox.getByRole('tab', { name: 'Testing' }).click(); + + const settingsView = await getWebview(workbox.locator('.pane', { has: workbox.getByLabel('Playwright Section') })); + await settingsView.getByLabel('Show trace viewer').check(); + await settingsView.getByLabel('Embedded').check(); + + const testExplorerPane = workbox.locator('.pane', { has: workbox.getByLabel('Test Explorer Section') }); + + const testItems = testExplorerPane.getByRole('treeitem'); + await testItems.filter({ hasText: /tests/ }).locator('.monaco-tl-twistie').click(); + await testItems.filter({ hasText: /spec.ts/ }).locator('.monaco-tl-twistie').click(); + await testItems.filter({ hasText: /has title/ }).hover(); + await testItems.filter({ hasText: /has title/ }).getByLabel('Run Test', { exact: true }).click(); + + const editorArea = workbox.getByRole('main'); + await expect(editorArea.getByRole('tab')).toHaveAccessibleName('Trace Viewer'); + const webviewPanel = await getWebview(editorArea); + const listItem = webviewPanel.frameLocator('iframe').getByTestId('actions-tree').getByRole('listitem'); + + await expect( + listItem, + 'action list' + ).toHaveText([ + /Before Hooks[\d.]+m?s/, + /page.goto.*[\d.]+m?s/, + /expect.toHaveTitle.*[\d.]+m?s/, + /After Hooks[\d.]+m?s/, + ]); +});