chore: introduce //lib/api.js (#3835)
Introduce `//lib/api.js` that declares a list of publicly exposed classes. The `//lib/api.js` list superceedes dynamic `helper.tracePublicAPI()` calls and is used in the following places: - [ASYNC STACKS]: generate "async stacks" for publicy exposed API in `//index.js` - [COVERAGE]: move coverage support from `//lib/helper` to `//test/utils` - [DOCLINT]: get rid of 'exluded classes' hardcoded list This will help us to re-use our coverage and doclint infrastructure for Puppeteer-Firefox. Drive-By: it turns out we didn't run coverage for `SecurityDetails` class, so we lack coverage for a few methods there. These are excluded for now, sanity tests will be added in a follow-up.
This commit is contained in:
parent
cd678fb591
commit
62da2366c6
10
index.js
10
index.js
@ -21,6 +21,16 @@ try {
|
||||
asyncawait = false;
|
||||
}
|
||||
|
||||
if (asyncawait) {
|
||||
const {helper} = require('./lib/helper');
|
||||
const api = require('./lib/api');
|
||||
for (const className in api) {
|
||||
// Puppeteer-web excludes certain classes from bundle, e.g. BrowserFetcher.
|
||||
if (typeof api[className] === 'function')
|
||||
helper.installAsyncStackHooks(api[className]);
|
||||
}
|
||||
}
|
||||
|
||||
// If node does not support async await, use the compiled version.
|
||||
const Puppeteer = asyncawait ? require('./lib/Puppeteer') : require('./node6/lib/Puppeteer');
|
||||
const packageJson = require('./package.json');
|
||||
|
@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const {helper} = require('./helper');
|
||||
|
||||
/**
|
||||
* @typedef {Object} SerializedAXNode
|
||||
@ -390,4 +389,3 @@ class AXNode {
|
||||
}
|
||||
|
||||
module.exports = {Accessibility};
|
||||
helper.tracePublicAPI(Accessibility);
|
||||
|
@ -373,7 +373,4 @@ class BrowserContext extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
helper.tracePublicAPI(BrowserContext);
|
||||
helper.tracePublicAPI(Browser);
|
||||
|
||||
module.exports = {Browser, BrowserContext};
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const {helper, assert} = require('./helper');
|
||||
const {assert} = require('./helper');
|
||||
const {Events} = require('./Events');
|
||||
const debugProtocol = require('debug')('puppeteer:protocol');
|
||||
const EventEmitter = require('events');
|
||||
@ -216,8 +216,6 @@ class CDPSession extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
helper.tracePublicAPI(CDPSession);
|
||||
|
||||
/**
|
||||
* @param {!Error} error
|
||||
* @param {string} method
|
||||
|
@ -64,7 +64,6 @@ class Coverage {
|
||||
}
|
||||
|
||||
module.exports = {Coverage};
|
||||
helper.tracePublicAPI(Coverage);
|
||||
|
||||
class JSCoverage {
|
||||
/**
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const {helper, assert} = require('./helper');
|
||||
const {assert} = require('./helper');
|
||||
|
||||
class Dialog {
|
||||
/**
|
||||
@ -81,4 +81,3 @@ Dialog.Type = {
|
||||
};
|
||||
|
||||
module.exports = {Dialog};
|
||||
helper.tracePublicAPI(Dialog);
|
||||
|
@ -175,6 +175,4 @@ class ExecutionContext {
|
||||
}
|
||||
}
|
||||
|
||||
helper.tracePublicAPI(ExecutionContext);
|
||||
|
||||
module.exports = {ExecutionContext, EVALUATION_SCRIPT_URL};
|
||||
|
@ -680,7 +680,6 @@ class Frame {
|
||||
this._parentFrame = null;
|
||||
}
|
||||
}
|
||||
helper.tracePublicAPI(Frame);
|
||||
|
||||
function assertNoLegacyNavigationOptions(options) {
|
||||
assert(options['networkIdleTimeout'] === undefined, 'ERROR: networkIdleTimeout option is no longer supported.');
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const {helper, assert} = require('./helper');
|
||||
const {assert} = require('./helper');
|
||||
const keyDefinitions = require('./USKeyboardLayout');
|
||||
|
||||
/**
|
||||
@ -302,6 +302,3 @@ class Touchscreen {
|
||||
}
|
||||
|
||||
module.exports = { Keyboard, Mouse, Touchscreen};
|
||||
helper.tracePublicAPI(Keyboard);
|
||||
helper.tracePublicAPI(Mouse);
|
||||
helper.tracePublicAPI(Touchscreen);
|
||||
|
@ -122,8 +122,6 @@ class JSHandle {
|
||||
}
|
||||
}
|
||||
|
||||
helper.tracePublicAPI(JSHandle);
|
||||
|
||||
class ElementHandle extends JSHandle {
|
||||
/**
|
||||
* @param {!Puppeteer.ExecutionContext} context
|
||||
@ -507,5 +505,4 @@ function computeQuadArea(quad) {
|
||||
* @property {number} height
|
||||
*/
|
||||
|
||||
helper.tracePublicAPI(ElementHandle);
|
||||
module.exports = {createJSHandle, JSHandle, ElementHandle};
|
||||
|
@ -507,8 +507,6 @@ const errorReasons = {
|
||||
'failed': 'Failed',
|
||||
};
|
||||
|
||||
helper.tracePublicAPI(Request);
|
||||
|
||||
class Response {
|
||||
/**
|
||||
* @param {!Puppeteer.CDPSession} client
|
||||
@ -649,7 +647,6 @@ class Response {
|
||||
return this._request.frame();
|
||||
}
|
||||
}
|
||||
helper.tracePublicAPI(Response);
|
||||
|
||||
const IGNORED_HEADERS = new Set(['accept', 'referer', 'x-devtools-emulate-network-conditions-client-id', 'cookie', 'origin', 'content-type', 'intervention']);
|
||||
|
||||
@ -798,4 +795,4 @@ const statusTexts = {
|
||||
'511': 'Network Authentication Required',
|
||||
};
|
||||
|
||||
module.exports = {Request, Response, NetworkManager};
|
||||
module.exports = {Request, Response, NetworkManager, SecurityDetails};
|
||||
|
@ -1270,5 +1270,4 @@ class ConsoleMessage {
|
||||
}
|
||||
|
||||
|
||||
module.exports = {Page};
|
||||
helper.tracePublicAPI(Page);
|
||||
module.exports = {Page, ConsoleMessage};
|
||||
|
@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const {helper} = require('./helper');
|
||||
const Launcher = require('./Launcher');
|
||||
const BrowserFetcher = require('./BrowserFetcher');
|
||||
|
||||
@ -68,4 +67,3 @@ module.exports = class {
|
||||
}
|
||||
};
|
||||
|
||||
helper.tracePublicAPI(module.exports, 'Puppeteer');
|
||||
|
@ -1,6 +1,21 @@
|
||||
/**
|
||||
* Copyright 2019 Google Inc. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const {Events} = require('./Events');
|
||||
const {Page} = require('./Page');
|
||||
const {helper} = require('./helper');
|
||||
|
||||
class Target {
|
||||
/**
|
||||
@ -113,6 +128,4 @@ class Target {
|
||||
}
|
||||
}
|
||||
|
||||
helper.tracePublicAPI(Target);
|
||||
|
||||
module.exports = {Target};
|
||||
|
@ -101,6 +101,5 @@ class Tracing {
|
||||
}
|
||||
}
|
||||
}
|
||||
helper.tracePublicAPI(Tracing);
|
||||
|
||||
module.exports = Tracing;
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
const EventEmitter = require('events');
|
||||
const {helper, debugError} = require('./helper');
|
||||
const {debugError} = require('./helper');
|
||||
const {ExecutionContext} = require('./ExecutionContext');
|
||||
const {JSHandle} = require('./JSHandle');
|
||||
|
||||
@ -78,4 +78,3 @@ class Worker extends EventEmitter {
|
||||
}
|
||||
|
||||
module.exports = {Worker};
|
||||
helper.tracePublicAPI(Worker);
|
||||
|
42
lib/api.js
Normal file
42
lib/api.js
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright 2019 Google Inc. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
Accessibility: require('./Accessibility').Accessibility,
|
||||
Browser: require('./Browser').Browser,
|
||||
BrowserContext: require('./Browser').BrowserContext,
|
||||
BrowserFetcher: require('./BrowserFetcher'),
|
||||
CDPSession: require('./Connection').CDPSession,
|
||||
ConsoleMessage: require('./Page').ConsoleMessage,
|
||||
Coverage: require('./Coverage').Coverage,
|
||||
Dialog: require('./Dialog').Dialog,
|
||||
ElementHandle: require('./JSHandle').ElementHandle,
|
||||
ExecutionContext: require('./ExecutionContext').ExecutionContext,
|
||||
Frame: require('./FrameManager').Frame,
|
||||
JSHandle: require('./JSHandle').JSHandle,
|
||||
Keyboard: require('./Input').Keyboard,
|
||||
Mouse: require('./Input').Mouse,
|
||||
Page: require('./Page').Page,
|
||||
Puppeteer: require('./Puppeteer'),
|
||||
Request: require('./NetworkManager').Request,
|
||||
Response: require('./NetworkManager').Response,
|
||||
SecurityDetails: require('./NetworkManager').SecurityDetails,
|
||||
Target: require('./Target').Target,
|
||||
TimeoutError: require('./Errors').TimeoutError,
|
||||
Touchscreen: require('./Input').Touchscreen,
|
||||
Tracing: require('./Tracing'),
|
||||
Worker: require('./Worker').Worker,
|
||||
};
|
@ -16,41 +16,6 @@
|
||||
const {TimeoutError} = require('./Errors');
|
||||
|
||||
const debugError = require('debug')(`puppeteer:error`);
|
||||
/** @type {?Map<string, boolean>} */
|
||||
let apiCoverage = null;
|
||||
|
||||
/**
|
||||
* @param {!Object} classType
|
||||
* @param {string=} publicName
|
||||
*/
|
||||
function traceAPICoverage(classType, publicName) {
|
||||
if (!apiCoverage)
|
||||
return;
|
||||
|
||||
let className = publicName || classType.prototype.constructor.name;
|
||||
className = className.substring(0, 1).toLowerCase() + className.substring(1);
|
||||
for (const methodName of Reflect.ownKeys(classType.prototype)) {
|
||||
const method = Reflect.get(classType.prototype, methodName);
|
||||
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function')
|
||||
continue;
|
||||
apiCoverage.set(`${className}.${methodName}`, false);
|
||||
Reflect.set(classType.prototype, methodName, function(...args) {
|
||||
apiCoverage.set(`${className}.${methodName}`, true);
|
||||
return method.call(this, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
if (classType.Events) {
|
||||
for (const event of Object.values(classType.Events))
|
||||
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, false);
|
||||
const method = Reflect.get(classType.prototype, 'emit');
|
||||
Reflect.set(classType.prototype, 'emit', function(event, ...args) {
|
||||
if (this.listenerCount(event))
|
||||
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true);
|
||||
return method.call(this, event, ...args);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Helper {
|
||||
/**
|
||||
@ -133,9 +98,8 @@ class Helper {
|
||||
|
||||
/**
|
||||
* @param {!Object} classType
|
||||
* @param {string=} publicName
|
||||
*/
|
||||
static tracePublicAPI(classType, publicName) {
|
||||
static installAsyncStackHooks(classType) {
|
||||
for (const methodName of Reflect.ownKeys(classType.prototype)) {
|
||||
const method = Reflect.get(classType.prototype, methodName);
|
||||
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function' || method.constructor.name !== 'AsyncFunction')
|
||||
@ -151,8 +115,6 @@ class Helper {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
traceAPICoverage(classType, publicName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,17 +137,6 @@ class Helper {
|
||||
listeners.splice(0, listeners.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {?Map<string, boolean>}
|
||||
*/
|
||||
static publicAPICoverage() {
|
||||
return apiCoverage;
|
||||
}
|
||||
|
||||
static recordPublicAPICoverage() {
|
||||
apiCoverage = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Object} obj
|
||||
* @return {boolean}
|
||||
|
30
test/test.js
30
test/test.js
@ -75,32 +75,24 @@ beforeEach(async({server, httpsServer}) => {
|
||||
httpsServer.reset();
|
||||
});
|
||||
|
||||
describe('Chromium', () => {
|
||||
const {helper} = require('../lib/helper');
|
||||
if (process.env.COVERAGE)
|
||||
helper.recordPublicAPICoverage();
|
||||
const CHROMIUM_NO_COVERAGE = new Set([
|
||||
'page.bringToFront',
|
||||
'securityDetails.subjectName',
|
||||
'securityDetails.issuer',
|
||||
'securityDetails.validFrom',
|
||||
'securityDetails.validTo',
|
||||
...(headless ? [] : ['page.pdf']),
|
||||
]);
|
||||
|
||||
describe('Chromium', () => {
|
||||
require('./puppeteer.spec.js').addTests({
|
||||
product: 'Chromium',
|
||||
puppeteer: utils.requireRoot('index'),
|
||||
defaultBrowserOptions,
|
||||
testRunner,
|
||||
});
|
||||
if (process.env.COVERAGE) {
|
||||
describe('COVERAGE', function() {
|
||||
const coverage = helper.publicAPICoverage();
|
||||
const disabled = new Set(['page.bringToFront']);
|
||||
if (!headless)
|
||||
disabled.add('page.pdf');
|
||||
|
||||
for (const method of coverage.keys()) {
|
||||
(disabled.has(method) ? xit : it)(`public api '${method}' should be called`, async({page, server}) => {
|
||||
if (!coverage.get(method))
|
||||
throw new Error('NOT CALLED!');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (process.env.COVERAGE)
|
||||
utils.recordAPICoverage(testRunner, require('../lib/api'), CHROMIUM_NO_COVERAGE);
|
||||
});
|
||||
|
||||
if (process.env.CI && testRunner.hasFocusedTestsOrSuites()) {
|
||||
|
@ -18,7 +18,51 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const PROJECT_ROOT = fs.existsSync(path.join(__dirname, '..', 'package.json')) ? path.join(__dirname, '..') : path.join(__dirname, '..', '..');
|
||||
|
||||
/**
|
||||
* @param {Map<string, boolean>} apiCoverage
|
||||
* @param {string} className
|
||||
* @param {!Object} classType
|
||||
*/
|
||||
function traceAPICoverage(apiCoverage, className, classType) {
|
||||
className = className.substring(0, 1).toLowerCase() + className.substring(1);
|
||||
for (const methodName of Reflect.ownKeys(classType.prototype)) {
|
||||
const method = Reflect.get(classType.prototype, methodName);
|
||||
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function')
|
||||
continue;
|
||||
apiCoverage.set(`${className}.${methodName}`, false);
|
||||
Reflect.set(classType.prototype, methodName, function(...args) {
|
||||
apiCoverage.set(`${className}.${methodName}`, true);
|
||||
return method.call(this, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
if (classType.Events) {
|
||||
for (const event of Object.values(classType.Events))
|
||||
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, false);
|
||||
const method = Reflect.get(classType.prototype, 'emit');
|
||||
Reflect.set(classType.prototype, 'emit', function(event, ...args) {
|
||||
if (this.listenerCount(event))
|
||||
apiCoverage.set(`${className}.emit(${JSON.stringify(event)})`, true);
|
||||
return method.call(this, event, ...args);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const utils = module.exports = {
|
||||
recordAPICoverage: function(testRunner, api, disabled) {
|
||||
const coverage = new Map();
|
||||
for (const [className, classType] of Object.entries(api))
|
||||
traceAPICoverage(coverage, className, classType);
|
||||
testRunner.describe('COVERAGE', () => {
|
||||
for (const method of coverage.keys()) {
|
||||
(disabled.has(method) ? testRunner.xit : testRunner.it)(`public api '${method}' should be called`, async({page, server}) => {
|
||||
if (!coverage.get(method))
|
||||
throw new Error('NOT CALLED!');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
|
@ -19,26 +19,6 @@ const mdBuilder = require('./MDBuilder');
|
||||
const Documentation = require('./Documentation');
|
||||
const Message = require('../Message');
|
||||
|
||||
const EXCLUDE_CLASSES = new Set([
|
||||
'AXNode',
|
||||
'CSSCoverage',
|
||||
'Connection',
|
||||
'CustomError',
|
||||
'DOMWorld',
|
||||
'EmulationManager',
|
||||
'FrameManager',
|
||||
'JSCoverage',
|
||||
'Helper',
|
||||
'Launcher',
|
||||
'Multimap',
|
||||
'LifecycleWatcher',
|
||||
'NetworkManager',
|
||||
'PipeTransport',
|
||||
'TaskQueue',
|
||||
'WaitTask',
|
||||
'WebSocketTransport',
|
||||
]);
|
||||
|
||||
const EXCLUDE_PROPERTIES = new Set([
|
||||
'Browser.create',
|
||||
'Headers.fromPayload',
|
||||
@ -55,7 +35,7 @@ const EXCLUDE_PROPERTIES = new Set([
|
||||
module.exports = async function lint(page, mdSources, jsSources) {
|
||||
const mdResult = await mdBuilder(page, mdSources);
|
||||
const jsResult = await jsBuilder(jsSources);
|
||||
const jsDocumentation = filterJSDocumentation(jsResult.documentation);
|
||||
const jsDocumentation = filterJSDocumentation(jsSources, jsResult.documentation);
|
||||
const mdDocumentation = mdResult.documentation;
|
||||
|
||||
const jsErrors = jsResult.errors;
|
||||
@ -126,14 +106,19 @@ function checkSorting(doc) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array<!Source>} jsSources
|
||||
* @param {!Documentation} jsDocumentation
|
||||
* @return {!Documentation}
|
||||
*/
|
||||
function filterJSDocumentation(jsDocumentation) {
|
||||
// Filter classes and methods.
|
||||
function filterJSDocumentation(jsSources, jsDocumentation) {
|
||||
const apijs = jsSources.find(source => source.name() === 'api.js');
|
||||
let includedClasses = null;
|
||||
if (apijs)
|
||||
includedClasses = new Set(Object.keys(require(apijs.filePath())));
|
||||
// Filter private classes and methods.
|
||||
const classes = [];
|
||||
for (const cls of jsDocumentation.classesArray) {
|
||||
if (EXCLUDE_CLASSES.has(cls.name))
|
||||
if (includedClasses && !includedClasses.has(cls.name))
|
||||
continue;
|
||||
const members = cls.membersArray.filter(member => !EXCLUDE_PROPERTIES.has(`${cls.name}.${member.name}`));
|
||||
classes.push(new Documentation.Class(cls.name, members));
|
||||
|
Loading…
Reference in New Issue
Block a user