feat(rpc): pass more tests (#2896)
Includes coverage, tracing and misc close() tests.
This commit is contained in:
parent
7039409397
commit
a91ec9a15d
|
|
@ -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)!;
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
42
src/rpc/client/coverage.ts
Normal file
42
src/rpc/client/coverage.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
30
src/types.ts
30
src/types.ts
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}) => {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
Loading…
Reference in a new issue