Expose client (browser) logs for smoke tests running in browser (fix #180102) (#181491)

This commit is contained in:
Benjamin Pasero 2023-05-04 12:05:03 +02:00 committed by GitHub
parent 75dee28a6d
commit 5eba2f631f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 98 additions and 27 deletions

View file

@ -6,11 +6,39 @@
import { getClientArea, getTopLeftOffset } from 'vs/base/browser/dom'; import { getClientArea, getTopLeftOffset } from 'vs/base/browser/dom';
import { coalesce } from 'vs/base/common/arrays'; import { coalesce } from 'vs/base/common/arrays';
import { language, locale } from 'vs/base/common/platform'; import { language, locale } from 'vs/base/common/platform';
import { IElement, ILocaleInfo, ILocalizedStrings, IWindowDriver } from 'vs/platform/driver/common/driver'; import { IElement, ILocaleInfo, ILocalizedStrings, ILogFile, IWindowDriver } from 'vs/platform/driver/common/driver';
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 localizedStrings from 'vs/platform/languagePacks/common/localizedStrings';
export class BrowserWindowDriver implements IWindowDriver { export class BrowserWindowDriver implements IWindowDriver {
constructor(
@IFileService private readonly fileService: IFileService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
}
async getLogs(): Promise<ILogFile[]> {
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;
}
async setValue(selector: string, text: string): Promise<void> { async setValue(selector: string, text: string): Promise<void> {
const element = document.querySelector(selector); const element = document.querySelector(selector);
@ -199,6 +227,6 @@ export class BrowserWindowDriver implements IWindowDriver {
} }
} }
export function registerWindowDriver(): void { export function registerWindowDriver(instantiationService: IInstantiationService): void {
Object.assign(window, { driver: new BrowserWindowDriver() }); Object.assign(window, { driver: instantiationService.createInstance(BrowserWindowDriver) });
} }

View file

@ -7,24 +7,29 @@
//*START //*START
export interface IElement { export interface IElement {
tagName: string; readonly tagName: string;
className: string; readonly className: string;
textContent: string; readonly textContent: string;
attributes: { [name: string]: string }; readonly attributes: { [name: string]: string };
children: IElement[]; readonly children: IElement[];
top: number; readonly top: number;
left: number; readonly left: number;
} }
export interface ILocaleInfo { export interface ILocaleInfo {
language: string; readonly language: string;
locale?: string; readonly locale?: string;
} }
export interface ILocalizedStrings { export interface ILocalizedStrings {
open: string; readonly open: string;
close: string; readonly close: string;
find: string; readonly find: string;
}
export interface ILogFile {
readonly name: string;
readonly contents: string;
} }
export interface IWindowDriver { export interface IWindowDriver {
@ -37,6 +42,7 @@ export interface IWindowDriver {
writeInTerminal(selector: string, text: string): Promise<void>; writeInTerminal(selector: string, text: string): Promise<void>;
getLocaleInfo(): Promise<ILocaleInfo>; getLocaleInfo(): Promise<ILocaleInfo>;
getLocalizedStrings(): Promise<ILocalizedStrings>; getLocalizedStrings(): Promise<ILocalizedStrings>;
getLogs(): Promise<ILogFile[]>;
exitApplication(): Promise<void>; exitApplication(): Promise<void>;
} }
//*END //*END

View file

@ -4,6 +4,9 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { BrowserWindowDriver } from 'vs/platform/driver/browser/driver'; import { BrowserWindowDriver } from 'vs/platform/driver/browser/driver';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
interface INativeWindowDriverHelper { interface INativeWindowDriverHelper {
exitApplication(): Promise<void>; exitApplication(): Promise<void>;
@ -11,8 +14,12 @@ interface INativeWindowDriverHelper {
class NativeWindowDriver extends BrowserWindowDriver { class NativeWindowDriver extends BrowserWindowDriver {
constructor(private readonly helper: INativeWindowDriverHelper) { constructor(
super(); private readonly helper: INativeWindowDriverHelper,
@IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService
) {
super(fileService, environmentService);
} }
override exitApplication(): Promise<void> { override exitApplication(): Promise<void> {
@ -20,6 +27,6 @@ class NativeWindowDriver extends BrowserWindowDriver {
} }
} }
export function registerWindowDriver(helper: INativeWindowDriverHelper): void { export function registerWindowDriver(instantiationService: IInstantiationService, helper: INativeWindowDriverHelper): void {
Object.assign(window, { driver: new NativeWindowDriver(helper) }); Object.assign(window, { driver: instantiationService.createInstance(NativeWindowDriver, helper) });
} }

View file

@ -18,7 +18,7 @@ import { localize } from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs';
import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; import { registerWindowDriver } from 'vs/platform/driver/browser/driver';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label'; import { ILabelService } from 'vs/platform/label/common/label';
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService'; import { IProductService } from 'vs/platform/product/common/productService';
@ -36,7 +36,8 @@ export class BrowserWindow extends Disposable {
@ILabelService private readonly labelService: ILabelService, @ILabelService private readonly labelService: ILabelService,
@IProductService private readonly productService: IProductService, @IProductService private readonly productService: IProductService,
@IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) { ) {
super(); super();
@ -129,7 +130,7 @@ export class BrowserWindow extends Disposable {
private setupDriver(): void { private setupDriver(): void {
if (this.environmentService.enableSmokeTestDriver) { if (this.environmentService.enableSmokeTestDriver) {
registerWindowDriver(); registerWindowDriver(this.instantiationService);
} }
} }

View file

@ -795,7 +795,7 @@ export class NativeWindow extends Disposable {
const that = this; const that = this;
let pendingQuit = false; let pendingQuit = false;
registerWindowDriver({ registerWindowDriver(this.instantiationService, {
async exitApplication(): Promise<void> { async exitApplication(): Promise<void> {
if (pendingQuit) { if (pendingQuit) {
that.logService.info('[driver] not handling exitApplication() due to pending quit() call'); that.logService.info('[driver] not handling exitApplication() due to pending quit() call');

View file

@ -6,7 +6,7 @@
import * as cp from 'child_process'; import * as cp from 'child_process';
import * as os from 'os'; import * as os from 'os';
import * as treekill from 'tree-kill'; import * as treekill from 'tree-kill';
import { IElement, ILocaleInfo, ILocalizedStrings } from './driver'; import { IElement, ILocaleInfo, ILocalizedStrings, ILogFile } from './driver';
import { Logger, measureAndLog } from './logger'; import { Logger, measureAndLog } from './logger';
import { launch as launchPlaywrightBrowser } from './playwrightBrowser'; import { launch as launchPlaywrightBrowser } from './playwrightBrowser';
import { PlaywrightDriver } from './playwrightDriver'; import { PlaywrightDriver } from './playwrightDriver';
@ -242,14 +242,18 @@ export class Code {
await this.poll(() => this.driver.writeInTerminal(selector, value), () => true, `writeInTerminal '${selector}'`); await this.poll(() => this.driver.writeInTerminal(selector, value), () => true, `writeInTerminal '${selector}'`);
} }
async getLocaleInfo(): Promise<ILocaleInfo> { getLocaleInfo(): Promise<ILocaleInfo> {
return this.driver.getLocaleInfo(); return this.driver.getLocaleInfo();
} }
async getLocalizedStrings(): Promise<ILocalizedStrings> { getLocalizedStrings(): Promise<ILocalizedStrings> {
return this.driver.getLocalizedStrings(); return this.driver.getLocalizedStrings();
} }
getLogs(): Promise<ILogFile[]> {
return this.driver.getLogs();
}
private async poll<T>( private async poll<T>(
fn: () => Promise<T>, fn: () => Promise<T>,
acceptFn: (result: T) => boolean, acceptFn: (result: T) => boolean,

View file

@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as playwright from '@playwright/test'; import * as playwright from '@playwright/test';
import { join } from 'path'; import { dirname, join } from 'path';
import { promises } from 'fs';
import { IWindowDriver } from './driver'; import { IWindowDriver } from './driver';
import { PageFunction } from 'playwright-core/types/structs'; import { PageFunction } from 'playwright-core/types/structs';
import { measureAndLog } from './logger'; import { measureAndLog } from './logger';
@ -107,6 +108,15 @@ export class PlaywrightDriver {
// Ignore // Ignore
} }
// Web: Extract client logs
if (this.options.web) {
try {
await measureAndLog(() => this.saveWebClientLogs(), 'saveWebClientLogs()', this.options.logger);
} catch (error) {
this.options.logger.log(`Error saving web client logs (${error})`);
}
}
// Web: exit via `close` method // Web: exit via `close` method
if (this.options.web) { if (this.options.web) {
try { try {
@ -131,6 +141,17 @@ export class PlaywrightDriver {
} }
} }
private async saveWebClientLogs(): Promise<void> {
const logs = await this.getLogs();
for (const log of logs) {
const absoluteLogsPath = join(this.options.logsPath, log.name);
await promises.mkdir(dirname(absoluteLogsPath), { recursive: true });
await promises.writeFile(absoluteLogsPath, log.contents);
}
}
async dispatchKeybinding(keybinding: string) { async dispatchKeybinding(keybinding: string) {
const chords = keybinding.split(' '); const chords = keybinding.split(' ');
for (let i = 0; i < chords.length; i++) { for (let i = 0; i < chords.length; i++) {
@ -206,6 +227,10 @@ export class PlaywrightDriver {
return this.evaluateWithDriver(([driver]) => driver.getLocalizedStrings()); return this.evaluateWithDriver(([driver]) => driver.getLocalizedStrings());
} }
async getLogs() {
return this.page.evaluate(([driver]) => driver.getLogs(), [await this.getDriverHandle()] as const);
}
private async evaluateWithDriver<T>(pageFunction: PageFunction<playwright.JSHandle<IWindowDriver>[], T>) { private async evaluateWithDriver<T>(pageFunction: PageFunction<playwright.JSHandle<IWindowDriver>[], T>) {
return this.page.evaluate(pageFunction, [await this.getDriverHandle()]); return this.page.evaluate(pageFunction, [await this.getDriverHandle()]);
} }