Skip to content

Commit

Permalink
test(inspector): add some tests
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Feb 15, 2021
1 parent 1f3449c commit 9cef7f5
Show file tree
Hide file tree
Showing 15 changed files with 175 additions and 51 deletions.
4 changes: 2 additions & 2 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ async function openPage(context: BrowserContext, url: string | undefined): Promi
}

async function open(options: Options, url: string | undefined, language: string) {
const { context, launchOptions, contextOptions } = await launchContext(options, false);
const { context, launchOptions, contextOptions } = await launchContext(options, !!process.env.PWCLI_HEADLESS_FOR_TEST);
await context._enableRecorder({
language,
launchOptions,
Expand All @@ -339,7 +339,7 @@ async function open(options: Options, url: string | undefined, language: string)
}

async function codegen(options: Options, url: string | undefined, language: string, outputFile?: string) {
const { context, launchOptions, contextOptions } = await launchContext(options, false);
const { context, launchOptions, contextOptions } = await launchContext(options, !!process.env.PWCLI_HEADLESS_FOR_TEST);
if (process.env.PWTRACE)
contextOptions._traceDir = path.join(process.cwd(), '.trace');
await context._enableRecorder({
Expand Down
1 change: 1 addition & 0 deletions src/server/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export type BrowserOptions = PlaywrightOptions & {
protocolLogger: types.ProtocolLogger,
browserLogsCollector: RecentLogsCollector,
slowMo?: number;
wsEndpoint?: string; // Only there when connected over web socket.
};

export abstract class Browser extends SdkObject {
Expand Down
24 changes: 19 additions & 5 deletions src/server/browserType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import path from 'path';
import * as util from 'util';
import { BrowserContext, normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
import * as registry from '../utils/registry';
import { ConnectionTransport } from './transport';
import { ConnectionTransport, WebSocketTransport } from './transport';
import { BrowserOptions, Browser, BrowserProcess, PlaywrightOptions } from './browser';
import { launchProcess, Env, envArrayToObject } from './processLauncher';
import { PipeTransport } from './pipeTransport';
Expand Down Expand Up @@ -112,6 +112,7 @@ export abstract class BrowserType extends SdkObject {
proxy: options.proxy,
protocolLogger,
browserLogsCollector,
wsEndpoint: options.useWebSocket ? (transport as WebSocketTransport).wsEndpoint : undefined,
};
if (persistent)
validateBrowserContextOptions(persistent, browserOptions);
Expand Down Expand Up @@ -180,6 +181,8 @@ export abstract class BrowserType extends SdkObject {
await validateHostRequirements(this._registry, this._name);
}

let wsEndpointCallback: ((wsEndpoint: string) => void) | undefined;
const wsEndpoint = options.useWebSocket ? new Promise<string>(f => wsEndpointCallback = f) : undefined;
// Note: it is important to define these variables before launchProcess, so that we don't get
// "Cannot access 'browserServer' before initialization" if something went wrong.
let transport: ConnectionTransport | undefined = undefined;
Expand All @@ -192,6 +195,11 @@ export abstract class BrowserType extends SdkObject {
handleSIGTERM,
handleSIGHUP,
log: (message: string) => {
if (wsEndpointCallback) {
const match = message.match(/DevTools listening on (.*)/);
if (match)
wsEndpointCallback(match[1]);
}
progress.log(message);
browserLogsCollector.log(message);
},
Expand All @@ -217,9 +225,12 @@ export abstract class BrowserType extends SdkObject {
kill
};
progress.cleanupWhenAborted(() => browserProcess && closeOrKill(browserProcess, progress.timeUntilDeadline()));

const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream];
transport = new PipeTransport(stdio[3], stdio[4]);
if (options.useWebSocket) {
transport = await WebSocketTransport.connect(progress, await wsEndpoint!);
} else {
const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream];
transport = new PipeTransport(stdio[3], stdio[4]);
}
return { browserProcess, downloadsPath, transport };
}

Expand All @@ -242,7 +253,10 @@ function copyTestHooks(from: object, to: object) {
}

function validateLaunchOptions<Options extends types.LaunchOptions>(options: Options): Options {
const { devtools = false, headless = !isDebugMode() && !devtools } = options;
const { devtools = false } = options;
let { headless = !devtools } = options;
if (isDebugMode())
headless = false;
return { ...options, devtools, headless };
}

Expand Down
5 changes: 4 additions & 1 deletion src/server/chromium/chromium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ export class Chromium extends BrowserType {
throw new Error('Arguments can not specify page to be opened');
const chromeArguments = [...DEFAULT_ARGS];
chromeArguments.push(`--user-data-dir=${userDataDir}`);
chromeArguments.push('--remote-debugging-pipe');
if (options.useWebSocket)
chromeArguments.push('--remote-debugging-port=0');
else
chromeArguments.push('--remote-debugging-pipe');
if (options.devtools)
chromeArguments.push('--auto-open-devtools-for-tabs');
if (options.headless) {
Expand Down
17 changes: 6 additions & 11 deletions src/server/supplements/inspectorController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,11 @@ import { CallMetadata, InstrumentationListener, SdkObject } from '../instrumenta
import { isDebugMode, isUnderTest } from '../../utils/utils';

export class InspectorController implements InstrumentationListener {
private _recorders = new Map<BrowserContext, Promise<RecorderSupplement>>();
private _waitOperations = new Map<string, CallMetadata>();

async onContextCreated(context: BrowserContext): Promise<void> {
if (isDebugMode())
this._recorders.set(context, RecorderSupplement.getOrCreate(context));
}

async onContextDidDestroy(context: BrowserContext): Promise<void> {
this._recorders.delete(context);
RecorderSupplement.getOrCreate(context);
}

async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
Expand Down Expand Up @@ -61,10 +56,10 @@ export class InspectorController implements InstrumentationListener {
// Force create recorder on pause.
if (!context._browser.options.headful && !isUnderTest())
return;
this._recorders.set(context, RecorderSupplement.getOrCreate(context));
RecorderSupplement.getOrCreate(context);
}

const recorder = await this._recorders.get(context);
const recorder = await RecorderSupplement.getNoCreate(context);
await recorder?.onBeforeCall(sdkObject, metadata);
}

Expand All @@ -87,22 +82,22 @@ export class InspectorController implements InstrumentationListener {
}
}

const recorder = await this._recorders.get(sdkObject.attribution.context);
const recorder = await RecorderSupplement.getNoCreate(sdkObject.attribution.context);
await recorder?.onAfterCall(metadata);
}

async onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
if (!sdkObject.attribution.context)
return;
const recorder = await this._recorders.get(sdkObject.attribution.context);
const recorder = await RecorderSupplement.getNoCreate(sdkObject.attribution.context);
await recorder?.onBeforeInputAction(metadata);
}

async onCallLog(logName: string, message: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
debugLogger.log(logName as any, message);
if (!sdkObject.attribution.context)
return;
const recorder = await this._recorders.get(sdkObject.attribution.context);
const recorder = await RecorderSupplement.getNoCreate(sdkObject.attribution.context);
await recorder?.updateCallLog([metadata]);
}
}
21 changes: 3 additions & 18 deletions src/server/supplements/recorder/recorderApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { internalCallMetadata } from '../../instrumentation';
import type { CallLog, EventData, Mode, Source } from './recorderTypes';
import { BrowserContext } from '../../browserContext';
import { isUnderTest } from '../../../utils/utils';
import { RecentLogsCollector } from '../../../utils/debugLogger';

const readFileAsync = util.promisify(fs.readFile);

Expand Down Expand Up @@ -104,34 +103,20 @@ export class RecorderApp extends EventEmitter {
sdkLanguage: inspectedContext._options.sdkLanguage,
args,
noDefaultViewport: true,
headless: isUnderTest() && !inspectedContext._browser.options.headful
headless: !!process.env.PWCLI_HEADLESS_FOR_TEST || (isUnderTest() && !inspectedContext._browser.options.headful),
useWebSocket: isUnderTest()
});
const wsEndpoint = isUnderTest() ? await this._parseWsEndpoint(context._browser.options.browserLogsCollector) : undefined;
const controller = new ProgressController(internalCallMetadata(), context._browser);
await controller.run(async progress => {
await context._browser._defaultContext!._loadDefaultContextAsIs(progress);
});

const [page] = context.pages();
const result = new RecorderApp(page, wsEndpoint);
const result = new RecorderApp(page, context._browser.options.wsEndpoint);
await result._init();
return result;
}

private static async _parseWsEndpoint(recentLogs: RecentLogsCollector): Promise<string> {
let callback: ((log: string) => void) | undefined;
const result = new Promise<string>(f => callback = f);
const check = (log: string) => {
const match = log.match(/DevTools listening on (.*)/);
if (match)
callback!(match[1]);
};
for (const log of recentLogs.recentLogs())
check(log);
recentLogs.on('log', check);
return result;
}

async setMode(mode: 'none' | 'recording' | 'inspecting'): Promise<void> {
await this._page.mainFrame()._evaluateExpression(((mode: Mode) => {
window.playwrightSetMode(mode);
Expand Down
14 changes: 13 additions & 1 deletion src/server/supplements/recorderSupplement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ export class RecorderSupplement {
return recorderPromise;
}

static getNoCreate(context: BrowserContext): Promise<RecorderSupplement> | undefined {
return (context as any)[symbol] as Promise<RecorderSupplement> | undefined;
}

constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) {
this._context = context;
this._params = params;
Expand Down Expand Up @@ -325,6 +329,8 @@ export class RecorderSupplement {
}

async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
if (this._mode === 'recording')
return;
this._currentCallsMetadata.set(metadata, sdkObject);
this._updateUserSources();
this.updateCallLog([metadata]);
Expand All @@ -333,6 +339,8 @@ export class RecorderSupplement {
}

async onAfterCall(metadata: CallMetadata): Promise<void> {
if (this._mode === 'recording')
return;
if (!metadata.error)
this._currentCallsMetadata.delete(metadata);
this._pausedCallsMetadata.delete(metadata);
Expand Down Expand Up @@ -372,16 +380,20 @@ export class RecorderSupplement {
}

async onBeforeInputAction(metadata: CallMetadata): Promise<void> {
if (this._mode === 'recording')
return;
if (this._pauseOnNextStatement)
await this.pause(metadata);
}

async updateCallLog(metadatas: CallMetadata[]): Promise<void> {
if (this._mode === 'recording')
return;
const logs: CallLog[] = [];
for (const metadata of metadatas) {
if (!metadata.method)
continue;
const title = metadata.stack?.[0]?.function || metadata.method;
const title = metadata.method;
let status: 'done' | 'in-progress' | 'paused' | 'error' = 'done';
if (this._currentCallsMetadata.has(metadata))
status = 'in-progress';
Expand Down
2 changes: 2 additions & 0 deletions src/server/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class WebSocketTransport implements ConnectionTransport {

onmessage?: (message: ProtocolResponse) => void;
onclose?: () => void;
readonly wsEndpoint: string;

static async connect(progress: Progress, url: string): Promise<WebSocketTransport> {
progress.log(`<ws connecting> ${url}`);
Expand All @@ -75,6 +76,7 @@ export class WebSocketTransport implements ConnectionTransport {
}

constructor(progress: Progress, url: string) {
this.wsEndpoint = url;
this._ws = new WebSocket(url, [], {
perMessageDeflate: false,
maxPayload: 256 * 1024 * 1024, // 256Mb,
Expand Down
3 changes: 2 additions & 1 deletion src/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ type LaunchOptionsBase = {
proxy?: ProxySettings,
downloadsPath?: string,
chromiumSandbox?: boolean,
slowMo?: number;
slowMo?: number,
useWebSocket?: boolean,
};
export type LaunchOptions = LaunchOptionsBase & {
firefoxUserPrefs?: { [key: string]: string | number | boolean },
Expand Down
4 changes: 1 addition & 3 deletions src/utils/debugLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import debug from 'debug';
import fs from 'fs';
import { EventEmitter } from 'events';

const debugLoggerColorMap = {
'api': 45, // cyan
Expand Down Expand Up @@ -64,11 +63,10 @@ class DebugLogger {
export const debugLogger = new DebugLogger();

const kLogCount = 50;
export class RecentLogsCollector extends EventEmitter {
export class RecentLogsCollector {
private _logs: string[] = [];

log(message: string) {
this.emit('log', message);
this._logs.push(message);
if (this._logs.length === kLogCount * 2)
this._logs.splice(0, kLogCount);
Expand Down
2 changes: 1 addition & 1 deletion test/browsertype-launch-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { folio } from './remoteServer.fixture';
const { it, expect, describe } = folio;

describe('lauch server', (suite, { mode }) => {
describe('launch server', (suite, { mode }) => {
suite.skip(mode !== 'default');
}, () => {
it('should work', async ({browserType, browserOptions}) => {
Expand Down
4 changes: 2 additions & 2 deletions test/cli/cli-codegen-1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import * as http from 'http';

const { it, describe, expect } = folio;

describe('cli codegen', (suite, { mode, browserName, headful }) => {
suite.fixme(browserName === 'firefox' && headful, 'Focus is off');
describe('cli codegen', (suite, { browserName, headful, mode }) => {
suite.skip(mode !== 'default');
suite.fixme(browserName === 'firefox' && headful, 'Focus is off');
}, () => {
it('should click', async ({ page, recorder }) => {
await recorder.setContentAndWait(`<button onclick="console.log('click')">Submit</button>`);
Expand Down
3 changes: 1 addition & 2 deletions test/cli/cli-codegen-2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ import * as url from 'url';

const { it, describe, expect } = folio;

describe('cli codegen', (suite, { mode, browserName, headful }) => {
// suite.fixme(browserName === 'firefox' && headful, 'Focus is off');
describe('cli codegen', (suite, { mode }) => {
suite.skip(mode !== 'default');
}, () => {
it('should contain open page', async ({ recorder }) => {
Expand Down
9 changes: 5 additions & 4 deletions test/cli/cli.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ class Recorder {
}
}

fixtures.runCLI.init(async ({ browserName }, runTest) => {
fixtures.runCLI.init(async ({ browserName, headful }, runTest) => {
let cli: CLIMock;
const cliFactory = (args: string[]) => {
cli = new CLIMock(browserName, args);
cli = new CLIMock(browserName, !headful, args);
return cli;
};
await runTest(cliFactory);
Expand All @@ -163,7 +163,7 @@ class CLIMock {
private waitForCallback: () => void;
exited: Promise<void>;

constructor(browserName, args: string[]) {
constructor(browserName: string, headless: boolean, args: string[]) {
this.data = '';
this.process = spawn('node', [
path.join(__dirname, '..', '..', 'lib', 'cli', 'cli.js'),
Expand All @@ -172,7 +172,8 @@ class CLIMock {
], {
env: {
...process.env,
PWCLI_EXIT_FOR_TEST: '1'
PWCLI_EXIT_FOR_TEST: '1',
PWCLI_HEADLESS_FOR_TEST: headless ? '1' : undefined,
},
stdio: 'pipe'
});
Expand Down

0 comments on commit 9cef7f5

Please sign in to comment.