From c4a46ef344946c29b4ad50a85b56e41585aebee2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 4 May 2023 15:28:25 +0200 Subject: [PATCH] Expose client (browser) logs for integration tests running in browser (#180102) (#181513) --- src/vs/platform/driver/browser/driver.ts | 18 ++-------- src/vs/platform/log/browser/log.ts | 33 ++++++++++++++++++- .../extensions/browser/extensionService.ts | 7 ++-- test/integration/browser/src/index.ts | 20 ++++++++--- 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/vs/platform/driver/browser/driver.ts b/src/vs/platform/driver/browser/driver.ts index 546187c2921..9b00c056de5 100644 --- a/src/vs/platform/driver/browser/driver.ts +++ b/src/vs/platform/driver/browser/driver.ts @@ -11,6 +11,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import localizedStrings from 'vs/platform/languagePacks/common/localizedStrings'; +import { getLogs } from 'vs/platform/log/browser/log'; export class BrowserWindowDriver implements IWindowDriver { @@ -21,22 +22,7 @@ export class BrowserWindowDriver implements IWindowDriver { } async getLogs(): Promise { - const result: ILogFile[] = []; - - const logs = await this.fileService.resolve(this.environmentService.logsHome); - - for (const { name, isDirectory, resource } of logs.children || []) { - if (isDirectory) { - continue; - } - - const contents = (await this.fileService.readFile(resource)).value.toString(); - if (contents) { - result.push({ name, contents }); - } - } - - return result; + return getLogs(this.fileService, this.environmentService); } async setValue(selector: string, text: string): Promise { diff --git a/src/vs/platform/log/browser/log.ts b/src/vs/platform/log/browser/log.ts index 1bb58dd4d44..42e4b34dc64 100644 --- a/src/vs/platform/log/browser/log.ts +++ b/src/vs/platform/log/browser/log.ts @@ -3,11 +3,42 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; import { AdapterLogger, DEFAULT_LOG_LEVEL, ILogger, LogLevel } from 'vs/platform/log/common/log'; export interface IAutomatedWindow { codeAutomationLog(type: string, args: any[]): void; - codeAutomationExit(code: number): void; + codeAutomationExit(logs: Array, code: number): void; +} + +export interface ILogFile { + readonly name: string; + readonly contents: string; +} + +/** + * Only used in browser contexts where the log files are not stored on disk + * but in IndexedDB. A method to get all logs with their contents so that + * CI automation can persist them. + */ +export async function getLogs(fileService: IFileService, environmentService: IEnvironmentService): Promise { + const result: ILogFile[] = []; + + const logs = await fileService.resolve(environmentService.logsHome); + + for (const { name, isDirectory, resource } of logs.children || []) { + if (isDirectory) { + continue; + } + + const contents = (await fileService.readFile(resource)).value.toString(); + if (contents) { + result.push({ name, contents }); + } + } + + return result; } function logLevelToString(level: LogLevel): string { diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 62e1359d147..d08879dc180 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -10,7 +10,7 @@ import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionDescription } import { IFileService } from 'vs/platform/files/common/files'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IAutomatedWindow } from 'vs/platform/log/browser/log'; +import { IAutomatedWindow, getLogs } from 'vs/platform/log/browser/log'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -139,13 +139,14 @@ export class ExtensionService extends AbstractExtensionService implements IExten return new ResolvedExtensions(localExtensions, remoteExtensions, /*hasLocalProcess*/false, /*allowRemoteExtensionsInLocalWebWorker*/true); } - protected _onExtensionHostExit(code: number): void { + protected async _onExtensionHostExit(code: number): Promise { // Dispose everything associated with the extension host this._doStopExtensionHosts(); + // If we are running extension tests, forward logs and exit code const automatedWindow = window as unknown as IAutomatedWindow; if (typeof automatedWindow.codeAutomationExit === 'function') { - automatedWindow.codeAutomationExit(code); + automatedWindow.codeAutomationExit(await getLogs(this._fileService, this._environmentService), code); } } } diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index 821b9862bd9..311a3465382 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -13,6 +13,10 @@ import { URI } from 'vscode-uri'; import * as kill from 'tree-kill'; import * as optimistLib from 'optimist'; import { promisify } from 'util'; +import { promises } from 'fs'; + +const root = path.join(__dirname, '..', '..', '..', '..'); +const logsPath = path.join(root, '.build', 'logs', 'integration-tests-browser'); const optimist = optimistLib .describe('workspacePath', 'path to the workspace (folder or *.code-workspace file) to open in the test').string('workspacePath') @@ -63,7 +67,18 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith console[type](...args); }); - await page.exposeFunction('codeAutomationExit', async (code: number) => { + await page.exposeFunction('codeAutomationExit', async (logs: Array<{ readonly name: string; readonly contents: string }>, code: number) => { + try { + for (const log of logs) { + const absoluteLogsPath = path.join(logsPath, log.name); + + await promises.mkdir(path.dirname(absoluteLogsPath), { recursive: true }); + await promises.writeFile(absoluteLogsPath, log.contents); + } + } catch (error) { + console.error(`Error saving web client logs (${error})`); + } + try { await browser.close(); } catch (error) { @@ -123,9 +138,6 @@ async function launchServer(browserType: BrowserType): Promise<{ endpoint: url.U ...process.env }; - const root = path.join(__dirname, '..', '..', '..', '..'); - const logsPath = path.join(root, '.build', 'logs', 'integration-tests-browser'); - const serverArgs = ['--enable-proposed-api', '--disable-telemetry', '--server-data-dir', userDataDir, '--accept-server-license-terms', '--disable-workspace-trust']; let serverLocation: string;