Add more logging and perf markers around resolving the connection token and the socket factory (#170490)

* Add more logging and perf markers around resolving the connection token and the socket factory

* set `exposeFunction` earlier

* bla windows

* also expose function for unit tests beofre opening

Co-authored-by: Benjamin Pasero <benjamin.pasero@microsoft.com>
This commit is contained in:
Alexandru Dima 2023-01-05 10:17:19 +01:00 committed by GitHub
parent 79c4f9b75d
commit 06b97f6be7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 91 additions and 57 deletions

View file

@ -6,9 +6,12 @@
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { RemoteAuthorities } from 'vs/base/common/network';
import * as performance from 'vs/base/common/performance';
import { StopWatch } from 'vs/base/common/stopwatch';
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { getRemoteServerRootPath, parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts';
export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService {
@ -23,7 +26,12 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
private readonly _connectionToken: Promise<string> | string | undefined;
private readonly _connectionTokens: Map<string, string>;
constructor(@IProductService productService: IProductService, connectionToken: Promise<string> | string | undefined, resourceUriProvider: ((uri: URI) => URI) | undefined) {
constructor(
connectionToken: Promise<string> | string | undefined,
resourceUriProvider: ((uri: URI) => URI) | undefined,
@IProductService productService: IProductService,
@ILogService private readonly _logService: ILogService,
) {
super();
this._connectionToken = connectionToken;
this._connectionTokens = new Map<string, string>();
@ -60,7 +68,13 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
}
private async _doResolveAuthority(authority: string): Promise<ResolverResult> {
const authorityPrefix = getRemoteAuthorityPrefix(authority);
const sw = StopWatch.create(false);
this._logService.info(`Resolving connection token (${authorityPrefix})...`);
performance.mark(`code/willResolveConnectionToken/${authorityPrefix}`);
const connectionToken = await Promise.resolve(this._connectionTokens.get(authority) || this._connectionToken);
performance.mark(`code/didResolveConnectionToken/${authorityPrefix}`);
this._logService.info(`Resolved connection token (${authorityPrefix}) after ${sw.elapsed()} ms`);
const defaultPort = (/^https:/.test(window.location.href) ? 443 : 80);
const { host, port } = parseAuthorityWithOptionalPort(authority, defaultPort);
const result: ResolverResult = { authority: { authority, host: host, port: port, connectionToken } };

View file

@ -9,6 +9,8 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance
import { isCancellationError, onUnexpectedError } from 'vs/base/common/errors';
import { Emitter } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import * as performance from 'vs/base/common/performance';
import { StopWatch } from 'vs/base/common/stopwatch';
import { generateUuid } from 'vs/base/common/uuid';
import { IIPCLogger } from 'vs/base/parts/ipc/common/ipc';
import { Client, ConnectionHealth, ISocket, PersistentProtocol, ProtocolConstants, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net';
@ -190,18 +192,27 @@ function readOneControlMessage<T>(protocol: PersistentProtocol, timeoutCancellat
return result.promise;
}
function createSocket(logService: ILogService, socketFactory: ISocketFactory, host: string, port: number, path: string, query: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise<ISocket> {
function createSocket(logService: ILogService, socketFactory: ISocketFactory, host: string, port: number, path: string, query: string, debugConnectionType: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise<ISocket> {
const result = new PromiseWithTimeout<ISocket>(timeoutCancellationToken);
const sw = StopWatch.create(false);
logService.info(`Creating a socket (${debugLabel})...`);
performance.mark(`code/willCreateSocket/${debugConnectionType}`);
socketFactory.connect(host, port, path, query, debugLabel, (err: any, socket: ISocket | undefined) => {
if (result.didTimeout) {
performance.mark(`code/didCreateSocketError/${debugConnectionType}`);
logService.info(`Creating a socket (${debugLabel}) finished after ${sw.elapsed()} ms, but this is too late and has timed out already.`);
if (err) {
logService.error(err);
}
socket?.dispose();
} else {
if (err || !socket) {
performance.mark(`code/didCreateSocketError/${debugConnectionType}`);
logService.info(`Creating a socket (${debugLabel}) returned an error after ${sw.elapsed()} ms.`);
result.reject(err);
} else {
performance.mark(`code/didCreateSocketOK/${debugConnectionType}`);
logService.info(`Creating a socket (${debugLabel}) was successful after ${sw.elapsed()} ms.`);
result.resolve(socket);
}
}
@ -233,7 +244,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio
let socket: ISocket;
try {
socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, getRemoteServerRootPath(options), `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken);
socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, getRemoteServerRootPath(options), `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, connectionTypeToString(connectionType), `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken);
} catch (error) {
options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`);
options.logService.error(error);

View file

@ -124,3 +124,11 @@ export interface IRemoteAuthorityResolverService {
_setAuthorityConnectionToken(authority: string, connectionToken: string): void;
_setCanonicalURIProvider(provider: (uri: URI) => Promise<URI>): void;
}
export function getRemoteAuthorityPrefix(remoteAuthority: string): string {
const plusIndex = remoteAuthority.indexOf('+');
if (plusIndex === -1) {
return remoteAuthority;
}
return remoteAuthority.substring(0, plusIndex);
}

View file

@ -26,7 +26,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
import { VSBuffer } from 'vs/base/common/buffer';
import { ExtensionGlobalMemento, ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
@ -1039,14 +1039,6 @@ function filterExtensions(globalRegistry: ExtensionDescriptionRegistry, desiredE
);
}
function getRemoteAuthorityPrefix(remoteAuthority: string): string {
const plusIndex = remoteAuthority.indexOf('+');
if (plusIndex === -1) {
return remoteAuthority;
}
return remoteAuthority.substring(0, plusIndex);
}
export class ExtensionPaths {
constructor(

View file

@ -247,7 +247,7 @@ export class BrowserMain extends Disposable {
// Remote
const connectionToken = environmentService.options.connectionToken || getCookieValue(connectionTokenCookieName);
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(productService, connectionToken, this.configuration.resourceUriProvider);
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(connectionToken, this.configuration.resourceUriProvider, productService, logService);
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
// Signing

View file

@ -88,7 +88,7 @@ suite('WorkspaceContextService - Folder', () => {
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const uriIdentityService = new UriIdentityService(fileService);
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
});
@ -130,7 +130,7 @@ suite('WorkspaceContextService - Folder', () => {
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const uriIdentityService = new UriIdentityService(fileService);
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a'));
@ -152,7 +152,7 @@ suite('WorkspaceContextService - Folder', () => {
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const uriIdentityService = new UriIdentityService(fileService);
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));

View file

@ -12,7 +12,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati
import { ExtHostCustomersRegistry, IInternalExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { Proxied, ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as nls from 'vs/nls';
import { registerAction2, Action2 } from 'vs/platform/actions/common/actions';
@ -27,7 +27,6 @@ import { ILogService } from 'vs/platform/log/common/log';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionHostProxy, IResolveAuthorityResult } from 'vs/workbench/services/extensions/common/extensionHostProxy';
import { IExtensionDescriptionDelta } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { getRemoteAuthorityPrefix } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;

View file

@ -285,10 +285,3 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
}
}
export function getRemoteAuthorityPrefix(remoteAuthority: string): string {
const plusIndex = remoteAuthority.indexOf('+');
if (plusIndex === -1) {
return remoteAuthority;
}
return remoteAuthority.substring(0, plusIndex);
}

View file

@ -6,14 +6,15 @@
import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner';
import { AbstractExtensionService, ExtensionHostCrashTracker, ExtensionRunningPreference, extensionRunningPreferenceToString, filterByRunningLocation } from 'vs/workbench/services/extensions/common/abstractExtensionService';
import * as nls from 'vs/nls';
import * as performance from 'vs/base/common/performance';
import { runWhenIdle } from 'vs/base/common/async';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IRemoteExtensionHostDataProvider, RemoteExtensionHost, IRemoteExtensionHostInitData, getRemoteAuthorityPrefix } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
import { IRemoteExtensionHostDataProvider, RemoteExtensionHost, IRemoteExtensionHostInitData } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
@ -424,15 +425,9 @@ export class NativeExtensionService extends AbstractExtensionService implements
const MAX_ATTEMPTS = 5;
for (let attempt = 1; ; attempt++) {
const sw = StopWatch.create(false);
this._logService.info(`[attempt ${attempt}] Invoking resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)})`);
try {
const resolverResult = await this._resolveAuthority(remoteAuthority);
this._logService.info(`[attempt ${attempt}] resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned '${resolverResult.authority.host}:${resolverResult.authority.port}' after ${sw.elapsed()} ms`);
return resolverResult;
return this._resolveAuthorityWithLogging(remoteAuthority);
} catch (err) {
this._logService.error(`[attempt ${attempt}] resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned an error after ${sw.elapsed()} ms`, err);
if (RemoteAuthorityResolverError.isNoResolverFound(err)) {
// There is no point in retrying if there is no resolver found
throw err;
@ -458,18 +453,31 @@ export class NativeExtensionService extends AbstractExtensionService implements
}
this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority);
const sw = StopWatch.create(false);
this._logService.info(`Invoking resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)})`);
try {
const result = await this._resolveAuthority(remoteAuthority);
this._logService.info(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned '${result.authority.host}:${result.authority.port}' after ${sw.elapsed()} ms`);
const result = await this._resolveAuthorityWithLogging(remoteAuthority);
this._remoteAuthorityResolverService._setResolvedAuthority(result.authority, result.options);
} catch (err) {
this._logService.error(`resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)}) returned an error after ${sw.elapsed()} ms`, err);
this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err);
}
}
private async _resolveAuthorityWithLogging(remoteAuthority: string): Promise<ResolverResult> {
const authorityPrefix = getRemoteAuthorityPrefix(remoteAuthority);
const sw = StopWatch.create(false);
this._logService.info(`Invoking resolveAuthority(${authorityPrefix})...`);
try {
performance.mark(`code/willResolveAuthority/${authorityPrefix}`);
const result = await this._resolveAuthority(remoteAuthority);
performance.mark(`code/didResolveAuthorityOK/${authorityPrefix}`);
this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority.host}:${result.authority.port}' after ${sw.elapsed()} ms`);
return result;
} catch (err) {
performance.mark(`code/didResolveAuthorityError/${authorityPrefix}`);
this._logService.error(`resolveAuthority(${authorityPrefix}) returned an error after ${sw.elapsed()} ms`, err);
throw err;
}
}
protected async _scanAndHandleExtensions(): Promise<void> {
this._extensionScanner.startScanningExtensions();
@ -485,12 +493,14 @@ export class NativeExtensionService extends AbstractExtensionService implements
// The current remote authority resolver cannot give the canonical URI for this URI
return uri;
}
performance.mark(`code/willGetCanonicalURI/${getRemoteAuthorityPrefix(remoteAuthority)}`);
if (isCI) {
this._logService.info(`Invoking getCanonicalURI for authority ${getRemoteAuthorityPrefix(remoteAuthority)}...`);
}
try {
return this._getCanonicalURI(remoteAuthority, uri);
} finally {
performance.mark(`code/didGetCanonicalURI/${getRemoteAuthorityPrefix(remoteAuthority)}`);
if (isCI) {
this._logService.info(`getCanonicalURI returned for authority ${getRemoteAuthorityPrefix(remoteAuthority)}.`);
}

View file

@ -35,6 +35,12 @@ type BrowserType = 'chromium' | 'firefox' | 'webkit';
async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWithStringQuery, server: cp.ChildProcess): Promise<void> {
const browser = await playwright[browserType].launch({ headless: !Boolean(optimist.argv.debug) });
const context = await browser.newContext();
try {
await context.grantPermissions([`clipboard-read`, `clipboard-write`]); // some tests need clipboard access
} catch (error) {
// ignore, seems to fail on Windows
}
const page = await context.newPage();
await page.setViewportSize({ width, height });
@ -58,21 +64,6 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith
console.error('Request Failed', e.url(), e.failure()?.errorText);
});
const host = endpoint.host;
const protocol = 'vscode-remote';
const testWorkspacePath = URI.file(path.resolve(optimist.argv.workspacePath)).path;
const testExtensionUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionDevelopmentPath)).path, protocol, host, slashes: true });
const testFilesUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionTestsPath)).path, protocol, host, slashes: true });
const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","ef65ac1ba57f57f2a3961bfe94aa20481caca4c6"],["skipWelcome","true"]]`;
if (path.extname(testWorkspacePath) === '.code-workspace') {
await page.goto(`${endpoint.href}&workspace=${testWorkspacePath}&payload=${payloadParam}`);
} else {
await page.goto(`${endpoint.href}&folder=${testWorkspacePath}&payload=${payloadParam}`);
}
await page.exposeFunction('codeAutomationLog', (type: string, args: any[]) => {
console[type](...args);
});
@ -92,6 +83,21 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith
process.exit(code);
});
const host = endpoint.host;
const protocol = 'vscode-remote';
const testWorkspacePath = URI.file(path.resolve(optimist.argv.workspacePath)).path;
const testExtensionUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionDevelopmentPath)).path, protocol, host, slashes: true });
const testFilesUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionTestsPath)).path, protocol, host, slashes: true });
const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","ef65ac1ba57f57f2a3961bfe94aa20481caca4c6"],["skipWelcome","true"]]`;
if (path.extname(testWorkspacePath) === '.code-workspace') {
await page.goto(`${endpoint.href}&workspace=${testWorkspacePath}&payload=${payloadParam}`);
} else {
await page.goto(`${endpoint.href}&folder=${testWorkspacePath}&payload=${payloadParam}`);
}
}
function consoleLogFn(msg: playwright.ConsoleMessage) {

View file

@ -133,13 +133,14 @@ async function runTestsInBrowser(testModules, browserType) {
if (argv.build) {
target.search = `?build=true`;
}
await page.goto(target.href);
const emitter = new events.EventEmitter();
await page.exposeFunction('mocha_report', (type, data1, data2) => {
emitter.emit(type, data1, data2);
});
await page.goto(target.href);
page.on('console', async msg => {
consoleLogFn(msg)(msg.text(), await Promise.all(msg.args().map(async arg => await arg.jsonValue())));
});
@ -176,7 +177,7 @@ async function runTestsInBrowser(testModules, browserType) {
await browser.close();
if (failingTests.length > 0) {
let res = `The followings tests are failing:\n - ${failingTests.map(({ title, message }) => `${title} (reason: ${message})`).join('\n - ')}`;
let res = `The followings tests are failing:\n - ${failingTests.map(({ title, message }) => `${title} (reason: ${message})`).join('\n - ')}`;
if (failingModuleIds.length > 0) {
res += `\n\nTo DEBUG, open ${browserType.toUpperCase()} and navigate to ${target.href}?${failingModuleIds.map(module => `m=${module}`).join('&')}`;