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 { coalesce } from 'vs/base/common/arrays';
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';
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> {
const element = document.querySelector(selector);
@ -199,6 +227,6 @@ export class BrowserWindowDriver implements IWindowDriver {
}
}
export function registerWindowDriver(): void {
Object.assign(window, { driver: new BrowserWindowDriver() });
export function registerWindowDriver(instantiationService: IInstantiationService): void {
Object.assign(window, { driver: instantiationService.createInstance(BrowserWindowDriver) });
}

View File

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

View File

@ -4,6 +4,9 @@
*--------------------------------------------------------------------------------------------*/
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 {
exitApplication(): Promise<void>;
@ -11,8 +14,12 @@ interface INativeWindowDriverHelper {
class NativeWindowDriver extends BrowserWindowDriver {
constructor(private readonly helper: INativeWindowDriverHelper) {
super();
constructor(
private readonly helper: INativeWindowDriverHelper,
@IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService
) {
super(fileService, environmentService);
}
override exitApplication(): Promise<void> {
@ -20,6 +27,6 @@ class NativeWindowDriver extends BrowserWindowDriver {
}
}
export function registerWindowDriver(helper: INativeWindowDriverHelper): void {
Object.assign(window, { driver: new NativeWindowDriver(helper) });
export function registerWindowDriver(instantiationService: IInstantiationService, helper: INativeWindowDriverHelper): void {
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 { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs';
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 { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
@ -36,7 +36,8 @@ export class BrowserWindow extends Disposable {
@ILabelService private readonly labelService: ILabelService,
@IProductService private readonly productService: IProductService,
@IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super();
@ -129,7 +130,7 @@ export class BrowserWindow extends Disposable {
private setupDriver(): void {
if (this.environmentService.enableSmokeTestDriver) {
registerWindowDriver();
registerWindowDriver(this.instantiationService);
}
}

View File

@ -795,7 +795,7 @@ export class NativeWindow extends Disposable {
const that = this;
let pendingQuit = false;
registerWindowDriver({
registerWindowDriver(this.instantiationService, {
async exitApplication(): Promise<void> {
if (pendingQuit) {
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 os from 'os';
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 { launch as launchPlaywrightBrowser } from './playwrightBrowser';
import { PlaywrightDriver } from './playwrightDriver';
@ -242,14 +242,18 @@ export class Code {
await this.poll(() => this.driver.writeInTerminal(selector, value), () => true, `writeInTerminal '${selector}'`);
}
async getLocaleInfo(): Promise<ILocaleInfo> {
getLocaleInfo(): Promise<ILocaleInfo> {
return this.driver.getLocaleInfo();
}
async getLocalizedStrings(): Promise<ILocalizedStrings> {
getLocalizedStrings(): Promise<ILocalizedStrings> {
return this.driver.getLocalizedStrings();
}
getLogs(): Promise<ILogFile[]> {
return this.driver.getLogs();
}
private async poll<T>(
fn: () => Promise<T>,
acceptFn: (result: T) => boolean,

View File

@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as playwright from '@playwright/test';
import { join } from 'path';
import { dirname, join } from 'path';
import { promises } from 'fs';
import { IWindowDriver } from './driver';
import { PageFunction } from 'playwright-core/types/structs';
import { measureAndLog } from './logger';
@ -107,6 +108,15 @@ export class PlaywrightDriver {
// 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
if (this.options.web) {
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) {
const chords = keybinding.split(' ');
for (let i = 0; i < chords.length; i++) {
@ -206,6 +227,10 @@ export class PlaywrightDriver {
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>) {
return this.page.evaluate(pageFunction, [await this.getDriverHandle()]);
}