feat(rpc): pass more tests (#2896)

Includes coverage, tracing and misc close() tests.
This commit is contained in:
Dmitry Gozman 2020-07-09 15:33:01 -07:00 committed by GitHub
parent 7039409397
commit a91ec9a15d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 148 additions and 53 deletions

View file

@ -21,30 +21,6 @@ import { Protocol } from './protocol';
import * as types from '../types';
import * as sourceMap from '../utils/sourceMap';
type JSRange = {
startOffset: number,
endOffset: number,
count: number
}
type CSSCoverageEntry = {
url: string,
text?: string,
ranges: {
start: number,
end: number
}[]
};
type JSCoverageEntry = {
url: string,
source?: string,
functions: {
functionName: string,
ranges: JSRange[]
}[]
};
export class CRCoverage {
private _jsCoverage: JSCoverage;
private _cssCoverage: CSSCoverage;
@ -58,7 +34,7 @@ export class CRCoverage {
return await this._jsCoverage.start(options);
}
async stopJSCoverage(): Promise<JSCoverageEntry[]> {
async stopJSCoverage(): Promise<types.JSCoverageEntry[]> {
return await this._jsCoverage.stop();
}
@ -66,7 +42,7 @@ export class CRCoverage {
return await this._cssCoverage.start(options);
}
async stopCSSCoverage(): Promise<CSSCoverageEntry[]> {
async stopCSSCoverage(): Promise<types.CSSCoverageEntry[]> {
return await this._cssCoverage.stop();
}
}
@ -134,7 +110,7 @@ class JSCoverage {
this._scriptSources.set(event.scriptId, response.scriptSource);
}
async stop(): Promise<JSCoverageEntry[]> {
async stop(): Promise<types.JSCoverageEntry[]> {
assert(this._enabled, 'JSCoverage is not enabled');
this._enabled = false;
const [profileResponse] = await Promise.all([
@ -145,7 +121,7 @@ class JSCoverage {
] as const);
helper.removeEventListeners(this._eventListeners);
const coverage: JSCoverageEntry[] = [];
const coverage: types.JSCoverageEntry[] = [];
for (const entry of profileResponse.result) {
if (!this._scriptIds.has(entry.scriptId))
continue;
@ -216,7 +192,7 @@ class CSSCoverage {
}
}
async stop(): Promise<CSSCoverageEntry[]> {
async stop(): Promise<types.CSSCoverageEntry[]> {
assert(this._enabled, 'CSSCoverage is not enabled');
this._enabled = false;
const ruleTrackingResponse = await this._client.send('CSS.stopRuleUsageTracking');
@ -241,7 +217,7 @@ class CSSCoverage {
});
}
const coverage: CSSCoverageEntry[] = [];
const coverage: types.CSSCoverageEntry[] = [];
for (const styleSheetId of this._stylesheetURLs.keys()) {
const url = this._stylesheetURLs.get(styleSheetId)!;
const text = this._stylesheetSources.get(styleSheetId)!;

View file

@ -64,6 +64,8 @@ export interface BrowserChannel extends Channel {
newContext(params: types.BrowserContextOptions): Promise<BrowserContextChannel>;
crNewBrowserCDPSession(): Promise<CDPSessionChannel>;
crStartTracing(params: { page?: PageChannel, path?: string, screenshots?: boolean, categories?: string[] }): Promise<void>;
crStopTracing(): Promise<Binary>;
}
export type BrowserInitializer = {};
@ -154,9 +156,13 @@ export interface PageChannel extends Channel {
mouseUp(params: { button?: types.MouseButton, clickCount?: number }): Promise<void>;
mouseClick(params: { x: number, y: number, delay?: number, button?: types.MouseButton, clickCount?: number }): Promise<void>;
// A11Y
accessibilitySnapshot(params: { interestingOnly?: boolean, root?: ElementHandleChannel }): Promise<types.SerializedAXNode | null>;
pdf: (params: types.PDFOptions) => Promise<Binary>;
crStartJSCoverage(params: types.JSCoverageOptions): Promise<void>;
crStopJSCoverage(): Promise<types.JSCoverageEntry[]>;
crStartCSSCoverage(params: types.CSSCoverageOptions): Promise<void>;
crStopCSSCoverage(): Promise<types.CSSCoverageEntry[]>;
}
export type PageInitializer = {

View file

@ -27,7 +27,7 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
readonly _contexts = new Set<BrowserContext>();
private _isConnected = true;
private _isClosedOrClosing = false;
private _closedPromise: Promise<void>;
static from(browser: BrowserChannel): Browser {
return (browser as any)._object;
@ -45,6 +45,7 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
this._isClosedOrClosing = true;
this._scope.dispose();
});
this._closedPromise = new Promise(f => this.once(Events.Browser.Disconnected, f));
}
async newContext(options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
@ -73,13 +74,22 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
}
async close(): Promise<void> {
if (this._isClosedOrClosing)
return;
this._isClosedOrClosing = true;
await this._channel.close();
if (!this._isClosedOrClosing) {
this._isClosedOrClosing = true;
await this._channel.close();
}
await this._closedPromise;
}
async newBrowserCDPSession(): Promise<CDPSession> {
return CDPSession.from(await this._channel.crNewBrowserCDPSession());
}
async startTracing(page?: Page, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) {
await this._channel.crStartTracing({ ...options, page: page ? page._channel : undefined });
}
async stopTracing(): Promise<Buffer> {
return Buffer.from(await this._channel.crStopTracing(), 'base64');
}
}

View file

@ -40,6 +40,8 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
private _pendingWaitForEvents = new Map<(error: Error) => void, string>();
_timeoutSettings = new TimeoutSettings();
_ownerPage: Page | undefined;
private _isClosedOrClosing = false;
private _closedPromise: Promise<void>;
static from(context: BrowserContextChannel): BrowserContext {
return (context as any)._object;
@ -60,6 +62,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
this._channel.on('close', () => this._onClose());
this._channel.on('page', page => this._onPage(Page.from(page)));
this._channel.on('route', ({ route, request }) => this._onRoute(network.Route.from(route), network.Request.from(request)));
this._closedPromise = new Promise(f => this.once(Events.BrowserContext.Close, f));
initializer.crBackgroundPages.forEach(p => {
const page = Page.from(p);
@ -211,6 +214,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
}
private async _onClose() {
this._isClosedOrClosing = true;
if (this._browser)
this._browser._contexts.delete(this);
@ -225,7 +229,11 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
}
async close(): Promise<void> {
await this._channel.close();
if (!this._isClosedOrClosing) {
this._isClosedOrClosing = true;
await this._channel.close();
}
await this._closedPromise;
}
async newCDPSession(page: Page): Promise<CDPSession> {

View file

@ -0,0 +1,42 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as types from '../../types';
import { PageChannel } from '../channels';
export class Coverage {
private _channel: PageChannel;
constructor(channel: PageChannel) {
this._channel = channel;
}
async startJSCoverage(options: types.JSCoverageOptions = {}) {
await this._channel.crStartJSCoverage(options);
}
async stopJSCoverage(): Promise<types.JSCoverageEntry[]> {
return await this._channel.crStopJSCoverage();
}
async startCSSCoverage(options: types.CSSCoverageOptions = {}) {
await this._channel.crStartCSSCoverage(options);
}
async stopCSSCoverage(): Promise<types.CSSCoverageEntry[]> {
return await this._channel.crStopCSSCoverage();
}
}

View file

@ -38,6 +38,7 @@ import { Func1, FuncOn, SmartHandle } from './jsHandle';
import { Request, Response, Route, RouteHandler } from './network';
import { FileChooser } from './fileChooser';
import { Buffer } from 'buffer';
import { Coverage } from './coverage';
export class Page extends ChannelOwner<PageChannel, PageInitializer> {
@ -54,6 +55,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
readonly accessibility: Accessibility;
readonly keyboard: Keyboard;
readonly mouse: Mouse;
readonly coverage: Coverage;
readonly _bindings = new Map<string, FunctionWithSource>();
private _pendingWaitForEvents = new Map<(error: Error) => void, string>();
private _timeoutSettings = new TimeoutSettings();
@ -72,6 +74,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
this.accessibility = new Accessibility(this._channel);
this.keyboard = new Keyboard(this._channel);
this.mouse = new Mouse(this._channel);
this.coverage = new Coverage(this._channel);
this._mainFrame = Frame.from(initializer.mainFrame);
this._mainFrame._page = this;

View file

@ -18,11 +18,12 @@ import { Browser, BrowserBase } from '../../browser';
import { BrowserContextBase } from '../../browserContext';
import { Events } from '../../events';
import * as types from '../../types';
import { BrowserChannel, BrowserContextChannel, BrowserInitializer, CDPSessionChannel } from '../channels';
import { BrowserChannel, BrowserContextChannel, BrowserInitializer, CDPSessionChannel, Binary } from '../channels';
import { BrowserContextDispatcher } from './browserContextDispatcher';
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
import { Dispatcher, DispatcherScope } from './dispatcher';
import { CRBrowser } from '../../chromium/crBrowser';
import { PageDispatcher } from './pageDispatcher';
export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> implements BrowserChannel {
constructor(scope: DispatcherScope, browser: BrowserBase) {
@ -45,4 +46,15 @@ export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> i
const crBrowser = this._object as CRBrowser;
return new CDPSessionDispatcher(this._scope, await crBrowser.newBrowserCDPSession());
}
async crStartTracing(params: { page?: PageDispatcher, path?: string, screenshots?: boolean, categories?: string[] }): Promise<void> {
const crBrowser = this._object as CRBrowser;
await crBrowser.startTracing(params.page ? params.page._object : undefined, params);
}
async crStopTracing(): Promise<Binary> {
const crBrowser = this._object as CRBrowser;
const buffer = await crBrowser.stopTracing();
return buffer.toString('base64');
}
}

View file

@ -31,6 +31,7 @@ import { RequestDispatcher, ResponseDispatcher, RouteDispatcher } from './networ
import { serializeResult, parseArgument } from './jsHandleDispatcher';
import { ElementHandleDispatcher, createHandle } from './elementHandlerDispatcher';
import { FileChooser } from '../../fileChooser';
import { CRCoverage } from '../../chromium/crCoverage';
export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements PageChannel {
private _page: Page;
@ -125,7 +126,7 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
});
}
async screenshot(params: types.ScreenshotOptions): Promise<string> {
async screenshot(params: types.ScreenshotOptions): Promise<Binary> {
return (await this._page.screenshot(params)).toString('base64');
}
@ -190,6 +191,26 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
return binary.toString('base64');
}
async crStartJSCoverage(params: types.JSCoverageOptions): Promise<void> {
const coverage = this._page.coverage as CRCoverage;
await coverage.startJSCoverage(params);
}
async crStopJSCoverage(): Promise<types.JSCoverageEntry[]> {
const coverage = this._page.coverage as CRCoverage;
return await coverage.stopJSCoverage();
}
async crStartCSSCoverage(params: types.CSSCoverageOptions): Promise<void> {
const coverage = this._page.coverage as CRCoverage;
await coverage.startCSSCoverage(params);
}
async crStopCSSCoverage(): Promise<types.CSSCoverageEntry[]> {
const coverage = this._page.coverage as CRCoverage;
return await coverage.stopCSSCoverage();
}
_onFrameAttached(frame: Frame) {
this._dispatchEvent('frameAttached', FrameDispatcher.from(this._scope, frame));
}

View file

@ -123,12 +123,6 @@ export type PDFOptions = {
path?: string,
}
export type CoverageEntry = {
url: string,
text: string,
ranges: {start: number, end: number}[]
};
export type CSSCoverageOptions = {
resetOnNavigation?: boolean,
};
@ -138,6 +132,30 @@ export type JSCoverageOptions = {
reportAnonymousScripts?: boolean,
};
export type JSRange = {
startOffset: number,
endOffset: number,
count: number
};
export type CSSCoverageEntry = {
url: string,
text?: string,
ranges: {
start: number,
end: number
}[]
};
export type JSCoverageEntry = {
url: string,
source?: string,
functions: {
functionName: string,
ranges: JSRange[]
}[]
};
export type InjectedScriptProgress = {
aborted: boolean,
log: (message: string) => void,

View file

@ -121,7 +121,7 @@ describe('BrowserContext', function() {
let error = await promise;
expect(error.message).toContain('Context closed');
});
it.fail(CHANNEL)('close() should be callable twice', async({browser}) => {
it('close() should be callable twice', async({browser}) => {
const context = await browser.newContext();
await Promise.all([
context.close(),

View file

@ -16,7 +16,7 @@
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = require('../utils').testOptions(browserType);
describe.skip(CHANNEL)('JSCoverage', function() {
describe('JSCoverage', function() {
it('should work', async function({page, server}) {
await page.coverage.startJSCoverage();
await page.goto(server.PREFIX + '/jscoverage/simple.html', { waitUntil: 'load' });
@ -88,7 +88,7 @@ describe.skip(CHANNEL)('JSCoverage', function() {
});
});
describe.skip(CHANNEL)('CSSCoverage', function() {
describe('CSSCoverage', function() {
it('should work', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/simple.html');

View file

@ -35,7 +35,7 @@ describe('ChromiumBrowserContext.createSession', function() {
await page.goto(server.EMPTY_PAGE);
expect(events.length).toBe(1);
});
it.skip(CHANNEL)('should enable and disable domains independently', async function({page, browser, server}) {
it('should enable and disable domains independently', async function({page, browser, server}) {
const client = await page.context().newCDPSession(page);
await client.send('Runtime.enable');
await client.send('Debugger.enable');

View file

@ -18,7 +18,7 @@ const fs = require('fs');
const path = require('path');
const {FFOX, CHROMIUM, WEBKIT, OUTPUT_DIR, CHANNEL} = require('../utils').testOptions(browserType);
describe.skip(CHANNEL)('Chromium.startTracing', function() {
describe('Chromium.startTracing', function() {
beforeEach(async function(state) {
state.outputFile = path.join(OUTPUT_DIR, `trace-${state.parallelIndex}.json`);
state.browser = await state.browserType.launch(state.defaultBrowserOptions);

View file

@ -23,7 +23,7 @@ const removeFolder = require('rimraf');
const mkdtempAsync = util.promisify(fs.mkdtemp);
const removeFolderAsync = util.promisify(removeFolder);
const {FFOX, CHROMIUM, WEBKIT} = utils.testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = utils.testOptions(browserType);
describe('browserType.launch({downloadsPath})', function() {
beforeEach(async(state) => {
@ -116,7 +116,6 @@ describe('browserType.launchPersistent({acceptDownloads})', function() {
expect(download.suggestedFilename()).toBe(`file.txt`);
const path = await download.path();
expect(path.startsWith(downloadsPath)).toBeTruthy();
await context.close();
});
it('should not delete downloads when the context closes', async({page, context}) => {

View file

@ -154,7 +154,7 @@ describe.fail(FFOX && WIN)('Page.Events.Crash', function() {
const error = await promise;
expect(error.message).toContain('Navigation failed because page crashed');
});
it.fail(CHANNEL)('should be able to close context when page crashes', async({page}) => {
it('should be able to close context when page crashes', async({page}) => {
await page.setContent(`<div>This page should crash</div>`);
crash(page);
await page.waitForEvent('crash');