chore: move shared utilities to src/utils (#3575)

This commit is contained in:
Dmitry Gozman 2020-08-22 07:07:13 -07:00 коммит произвёл GitHub
Родитель b909924a61
Коммит 655013d025
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
66 изменённых файлов: 341 добавлений и 324 удалений

Просмотреть файл

@ -20,7 +20,7 @@ import { helper } from './helper';
import * as network from './network';
import * as path from 'path';
import { Page, PageBinding } from './page';
import { TimeoutSettings } from './timeoutSettings';
import { TimeoutSettings } from './utils/timeoutSettings';
import * as frames from './frames';
import * as types from './types';
import { Download } from './download';
@ -28,6 +28,7 @@ import { Browser } from './browser';
import { EventEmitter } from 'events';
import { Progress } from './progress';
import { DebugController } from './debug/debugController';
import { isDebugMode } from './utils/utils';
export class Screencast {
readonly path: string;
@ -68,7 +69,7 @@ export abstract class BrowserContext extends EventEmitter {
}
async _initialize() {
if (helper.isDebugMode())
if (isDebugMode())
new DebugController(this);
}
@ -250,24 +251,16 @@ export function verifyGeolocation(geolocation?: types.Geolocation) {
return;
geolocation.accuracy = geolocation.accuracy || 0;
const { longitude, latitude, accuracy } = geolocation;
if (!helper.isNumber(longitude))
throw new Error(`geolocation.longitude: expected number, got ${typeof longitude}`);
if (longitude < -180 || longitude > 180)
throw new Error(`geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed.`);
if (!helper.isNumber(latitude))
throw new Error(`geolocation.latitude: expected number, got ${typeof latitude}`);
if (latitude < -90 || latitude > 90)
throw new Error(`geolocation.latitude: precondition -90 <= LATITUDE <= 90 failed.`);
if (!helper.isNumber(accuracy))
throw new Error(`geolocation.accuracy: expected number, got ${typeof accuracy}`);
if (accuracy < 0)
throw new Error(`geolocation.accuracy: precondition 0 <= ACCURACY failed.`);
}
export function verifyProxySettings(proxy: types.ProxySettings): types.ProxySettings {
let { server, bypass } = proxy;
if (!helper.isString(server))
throw new Error(`Invalid proxy.server: ` + server);
let url = new URL(server);
if (!['http:', 'https:', 'socks5:'].includes(url.protocol)) {
url = new URL('http://' + server);

Просмотреть файл

@ -17,7 +17,7 @@
import { Browser, BrowserOptions } from '../browser';
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
import { assert } from '../helper';
import { assert } from '../utils/utils';
import * as network from '../network';
import { Page, PageBinding, Worker } from '../page';
import { ConnectionTransport } from '../transport';

Просмотреть файл

@ -15,11 +15,12 @@
* limitations under the License.
*/
import { assert, debugLogger } from '../helper';
import { assert } from '../utils/utils';
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import { Protocol } from './protocol';
import { EventEmitter } from 'events';
import { rewriteErrorMessage } from '../utils/stackTrace';
import { debugLogger } from '../utils/debugLogger';
export const ConnectionEvents = {
Disconnected: Symbol('ConnectionEvents.Disconnected')

Просмотреть файл

@ -16,10 +16,11 @@
*/
import { CRSession } from './crConnection';
import { assert, helper, RegisteredListener } from '../helper';
import { helper, RegisteredListener } from '../helper';
import { Protocol } from './protocol';
import * as types from '../types';
import * as sourceMap from '../utils/sourceMap';
import { assert } from '../utils/utils';
export class CRCoverage {
private _jsCoverage: JSCoverage;

Просмотреть файл

@ -19,7 +19,7 @@ import * as input from '../input';
import * as types from '../types';
import { CRSession } from './crConnection';
import { macEditingCommands } from '../macEditingCommands';
import { helper } from '../helper';
import { isString } from '../utils/utils';
function toModifiersMask(modifiers: Set<types.KeyboardModifier>): number {
let mask = 0;
@ -51,7 +51,7 @@ export class RawKeyboardImpl implements input.RawKeyboard {
parts.push(code);
const shortcut = parts.join('+');
let commands = macEditingCommands[shortcut] || [];
if (helper.isString(commands))
if (isString(commands))
commands = [commands];
// remove the trailing : to match the Chromium command names.
return commands.map(c => c.substring(0, c.length - 1));

Просмотреть файл

@ -17,13 +17,13 @@
import { CRSession } from './crConnection';
import { Page } from '../page';
import { assert, helper, RegisteredListener } from '../helper';
import { helper, RegisteredListener } from '../helper';
import { Protocol } from './protocol';
import * as network from '../network';
import * as frames from '../frames';
import * as types from '../types';
import { CRPage } from './crPage';
import { headersObjectToArray } from '../converters';
import { assert, headersObjectToArray } from '../utils/utils';
export class CRNetworkManager {
private _client: CRSession;

Просмотреть файл

@ -17,7 +17,7 @@
import * as dom from '../dom';
import * as frames from '../frames';
import { helper, RegisteredListener, assert } from '../helper';
import { helper, RegisteredListener } from '../helper';
import * as network from '../network';
import { CRSession, CRConnection, CRSessionEvents } from './crConnection';
import { CRExecutionContext } from './crExecutionContext';
@ -36,7 +36,7 @@ import * as types from '../types';
import { ConsoleMessage } from '../console';
import * as sourceMap from '../utils/sourceMap';
import { rewriteErrorMessage } from '../utils/stackTrace';
import { headersArrayToObject } from '../converters';
import { assert, headersArrayToObject } from '../utils/utils';
const UTILITY_WORLD_NAME = '__playwright_utility_world__';

Просмотреть файл

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { assert } from '../helper';
import { assert } from '../utils/utils';
import * as types from '../types';
import { CRSession } from './crConnection';
import { readProtocolStream } from './crProtocolHelper';

Просмотреть файл

@ -20,7 +20,7 @@ import { Protocol } from './protocol';
import * as fs from 'fs';
import * as util from 'util';
import * as types from '../types';
import { mkdirIfNeeded } from '../helper';
import { mkdirIfNeeded } from '../utils/utils';
export function getExceptionMessage(exceptionDetails: Protocol.Runtime.ExceptionDetails): string {
if (exceptionDetails.exception)

Просмотреть файл

@ -1,33 +0,0 @@
/**
* 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';
export function headersObjectToArray(headers: { [key: string]: string }): types.HeadersArray {
const result: types.HeadersArray = [];
for (const name in headers) {
if (!Object.is(headers[name], undefined))
result.push({ name, value: headers[name] });
}
return result;
}
export function headersArrayToObject(headers: types.HeadersArray, lowerCase: boolean): { [key: string]: string } {
const result: { [key: string]: string } = {};
for (const { name, value } of headers)
result[lowerCase ? name.toLowerCase() : name] = value;
return result;
}

Просмотреть файл

@ -15,7 +15,8 @@
* limitations under the License.
*/
import { assert, debugLogger } from './helper';
import { assert } from './utils/utils';
import { debugLogger } from './utils/debugLogger';
type OnHandle = (accept: boolean, promptText?: string) => Promise<void>;

Просмотреть файл

@ -15,7 +15,7 @@
*/
import * as frames from './frames';
import { assert, helper } from './helper';
import { assert } from './utils/utils';
import InjectedScript from './injected/injectedScript';
import * as injectedScriptSource from './generated/injectedScriptSource';
import * as debugScriptSource from './generated/debugScriptSource';
@ -429,7 +429,6 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
async _fill(progress: Progress, value: string, options: types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
progress.log(`elementHandle.fill("${value}")`);
assert(helper.isString(value), `value: expected string, got ${typeof value}`);
return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
progress.log(' waiting for element to be visible, enabled and editable');
const poll = await this._evaluateHandleInUtility(([injected, node, value]) => {

Просмотреть файл

@ -19,7 +19,7 @@ import * as fs from 'fs';
import * as util from 'util';
import { Page } from './page';
import { Readable } from 'stream';
import { assert, mkdirIfNeeded } from './helper';
import { assert, mkdirIfNeeded } from './utils/utils';
export class Download {
private _downloadsPath: string;

Просмотреть файл

@ -17,7 +17,8 @@
import { Browser, BrowserOptions } from '../browser';
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
import { assert, helper, RegisteredListener } from '../helper';
import { helper, RegisteredListener } from '../helper';
import { assert } from '../utils/utils';
import * as network from '../network';
import { Page, PageBinding } from '../page';
import { ConnectionTransport } from '../transport';

Просмотреть файл

@ -16,10 +16,11 @@
*/
import { EventEmitter } from 'events';
import { assert, debugLogger } from '../helper';
import { assert } from '../utils/utils';
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import { Protocol } from './protocol';
import { rewriteErrorMessage } from '../utils/stackTrace';
import { debugLogger } from '../utils/debugLogger';
export const ConnectionEvents = {
Disconnected: Symbol('Disconnected'),

Просмотреть файл

@ -18,7 +18,8 @@
import * as dialog from '../dialog';
import * as dom from '../dom';
import * as frames from '../frames';
import { assert, helper, RegisteredListener } from '../helper';
import { helper, RegisteredListener } from '../helper';
import { assert } from '../utils/utils';
import { Page, PageBinding, PageDelegate, Worker } from '../page';
import { kScreenshotDuringNavigationError } from '../screenshotter';
import * as types from '../types';

Просмотреть файл

@ -17,7 +17,7 @@
import { ConsoleMessage } from './console';
import * as dom from './dom';
import { assert, helper, RegisteredListener, debugLogger } from './helper';
import { helper, RegisteredListener } from './helper';
import * as js from './javascript';
import * as network from './network';
import { Page } from './page';
@ -26,6 +26,8 @@ import * as types from './types';
import { BrowserContext } from './browserContext';
import { Progress, ProgressController } from './progress';
import { EventEmitter } from 'events';
import { assert, makeWaitForNextTask } from './utils/utils';
import { debugLogger } from './utils/debugLogger';
type ContextData = {
contextPromise: Promise<dom.FrameExecutionContext>;
@ -134,7 +136,7 @@ export class FrameManager {
await barrier.waitFor();
this._signalBarriers.delete(barrier);
// Resolve in the next task, after all waitForNavigations.
await new Promise(helper.makeWaitForNextTask());
await new Promise(makeWaitForNextTask());
return result;
}
@ -879,20 +881,15 @@ export class Frame extends EventEmitter {
}
async _waitForFunctionExpression<R>(expression: string, isFunction: boolean, arg: any, options: types.WaitForFunctionOptions = {}): Promise<js.SmartHandle<R>> {
const { polling = 'raf' } = options;
if (helper.isString(polling))
assert(polling === 'raf', 'Unknown polling option: ' + polling);
else if (helper.isNumber(polling))
assert(polling > 0, 'Cannot poll with non-positive interval: ' + polling);
else
throw new Error('Unknown polling option: ' + polling);
if (typeof options.pollingInterval === 'number')
assert(options.pollingInterval > 0, 'Cannot poll with non-positive interval: ' + options.pollingInterval);
const predicateBody = isFunction ? 'return (' + expression + ')(arg)' : 'return (' + expression + ')';
const task: dom.SchedulableTask<R> = injectedScript => injectedScript.evaluateHandle((injectedScript, { predicateBody, polling, arg }) => {
const innerPredicate = new Function('arg', predicateBody) as (arg: any) => R;
if (polling === 'raf')
if (typeof polling !== 'number')
return injectedScript.pollRaf((progress, continuePolling) => innerPredicate(arg) || continuePolling);
return injectedScript.pollInterval(polling, (progress, continuePolling) => innerPredicate(arg) || continuePolling);
}, { predicateBody, polling, arg });
}, { predicateBody, polling: options.pollingInterval, arg });
return this._page._runAbortableTask(
progress => this._scheduleRerunnableHandleTask(progress, 'main', task),
this._page._timeoutSettings.timeout(options));

Просмотреть файл

@ -21,14 +21,11 @@ import * as fs from 'fs';
import * as os from 'os';
import * as removeFolder from 'rimraf';
import * as util from 'util';
import * as path from 'path';
import * as types from './types';
import { Progress } from './progress';
import * as debug from 'debug';
const removeFolderAsync = util.promisify(removeFolder);
const readFileAsync = util.promisify(fs.readFile.bind(fs));
const mkdirAsync = util.promisify(fs.mkdir.bind(fs));
export type RegisteredListener = {
emitter: EventEmitter;
@ -36,10 +33,6 @@ export type RegisteredListener = {
handler: (...args: any[]) => void;
};
export type Listener = (...args: any[]) => void;
const isInDebugMode = !!getFromENV('PWDEBUG');
class Helper {
static addEventListener(
emitter: EventEmitter,
@ -59,30 +52,6 @@ class Helper {
listeners.splice(0, listeners.length);
}
static isString(obj: any): obj is string {
return typeof obj === 'string' || obj instanceof String;
}
static isNumber(obj: any): obj is number {
return typeof obj === 'number' || obj instanceof Number;
}
static isRegExp(obj: any): obj is RegExp {
return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
}
static isError(obj: any): obj is Error {
return obj instanceof Error || (obj && obj.__proto__ && obj.__proto__.name === 'Error');
}
static isObject(obj: any): obj is NonNullable<object> {
return typeof obj === 'object' && obj !== null;
}
static isBoolean(obj: any): obj is boolean {
return typeof obj === 'boolean' || obj instanceof Boolean;
}
static completeUserURL(urlString: string): string {
if (urlString.startsWith('localhost') || urlString.startsWith('127.0.0.1'))
urlString = 'http://' + urlString;
@ -101,41 +70,6 @@ class Helper {
return { width: Math.floor(size.width + 1e-3), height: Math.floor(size.height + 1e-3) };
}
// See https://joel.tools/microtasks/
static makeWaitForNextTask() {
if (parseInt(process.versions.node, 10) >= 11)
return setImmediate;
// Unlike Node 11, Node 10 and less have a bug with Task and MicroTask execution order:
// - https://github.com/nodejs/node/issues/22257
//
// So we can't simply run setImmediate to dispatch code in a following task.
// However, we can run setImmediate from-inside setImmediate to make sure we're getting
// in the following task.
let spinning = false;
const callbacks: (() => void)[] = [];
const loop = () => {
const callback = callbacks.shift();
if (!callback) {
spinning = false;
return;
}
setImmediate(loop);
// Make sure to call callback() as the last thing since it's
// untrusted code that might throw.
callback();
};
return (callback: () => void) => {
callbacks.push(callback);
if (!spinning) {
spinning = true;
setImmediate(loop);
}
};
}
static guid(): string {
return crypto.randomBytes(16).toString('hex');
}
@ -176,10 +110,6 @@ class Helper {
progress.cleanupWhenAborted(dispose);
return { promise, dispose };
}
static isDebugMode(): boolean {
return isInDebugMode;
}
}
export async function getUbuntuVersion(): Promise<string> {
@ -221,87 +151,4 @@ function getUbuntuVersionInternal(osReleaseText: string): string {
return fields.get('version_id') || '';
}
export function assert(value: any, message?: string): asserts value {
if (!value)
throw new Error(message);
}
let _isUnderTest = false;
export function setUnderTest() {
_isUnderTest = true;
}
export function isUnderTest(): boolean {
return _isUnderTest;
}
export function debugAssert(value: any, message?: string): asserts value {
if (_isUnderTest && !value)
throw new Error(message);
}
export function getFromENV(name: string) {
let value = process.env[name];
value = value || process.env[`npm_config_${name.toLowerCase()}`];
value = value || process.env[`npm_package_config_${name.toLowerCase()}`];
return value;
}
export async function doSlowMo(amount?: number) {
if (!amount)
return;
await new Promise(x => setTimeout(x, amount));
}
export async function mkdirIfNeeded(filePath: string) {
// This will harmlessly throw on windows if the dirname is the root directory.
await mkdirAsync(path.dirname(filePath), {recursive: true}).catch(() => {});
}
export const helper = Helper;
const debugLoggerColorMap = {
'api': 45, // cyan
'protocol': 34, // green
'browser': 0, // reset
'error': 160, // red,
'channel:command': 33, // blue
'channel:response': 202, // orange
'channel:event': 207, // magenta
};
export type LogName = keyof typeof debugLoggerColorMap;
export class DebugLogger {
private _debuggers = new Map<string, debug.IDebugger>();
constructor() {
if (process.env.DEBUG_FILE) {
const ansiRegex = new RegExp([
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
].join('|'), 'g');
const stream = fs.createWriteStream(process.env.DEBUG_FILE);
(debug as any).log = (data: string) => {
stream.write(data.replace(ansiRegex, ''));
stream.write('\n');
};
}
}
log(name: LogName, message: string | Error | object) {
let cachedDebugger = this._debuggers.get(name);
if (!cachedDebugger) {
cachedDebugger = debug(`pw:${name}`);
this._debuggers.set(name, cachedDebugger);
(cachedDebugger as any).color = debugLoggerColorMap[name];
}
cachedDebugger(message);
}
isEnabled(name: LogName) {
return debug.enabled(`pw:${name}`);
}
}
export const debugLogger = new DebugLogger();

Просмотреть файл

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { assert } from './helper';
import { assert } from './utils/utils';
import * as keyboardLayout from './usKeyboardLayout';
import * as types from './types';
import type { Page } from './page';

Просмотреть файл

@ -23,7 +23,7 @@ import * as ProgressBar from 'progress';
import { getProxyForUrl } from 'proxy-from-env';
import * as URL from 'url';
import * as util from 'util';
import { assert, getFromENV } from '../helper';
import { assert, getFromENV } from '../utils/utils';
import * as browserPaths from './browserPaths';
import { BrowserName, BrowserPlatform, BrowserDescriptor } from './browserPaths';

Просмотреть файл

@ -18,7 +18,8 @@
import { execSync } from 'child_process';
import * as os from 'os';
import * as path from 'path';
import { getFromENV, getUbuntuVersionSync } from '../helper';
import { getUbuntuVersionSync } from '../helper';
import { getFromENV } from '../utils/utils';
export type BrowserName = 'chromium'|'webkit'|'firefox';
export type BrowserPlatform = 'win32'|'win64'|'mac10.13'|'mac10.14'|'mac10.15'|'ubuntu18.04'|'ubuntu20.04';

Просмотреть файл

@ -15,13 +15,13 @@
*/
import * as crypto from 'crypto';
import { getFromENV } from '../helper';
import * as fs from 'fs';
import * as path from 'path';
import * as util from 'util';
import * as removeFolder from 'rimraf';
import * as browserPaths from './browserPaths';
import * as browserFetcher from './browserFetcher';
import { getFromENV } from '../utils/utils';
const fsMkdirAsync = util.promisify(fs.mkdir.bind(fs));
const fsReaddirAsync = util.promisify(fs.readdir.bind(fs));

Просмотреть файл

@ -16,7 +16,7 @@
import * as frames from './frames';
import * as types from './types';
import { assert } from './helper';
import { assert } from './utils/utils';
export function filterCookies(cookies: types.NetworkCookie[], urls: string[]): types.NetworkCookie[] {
const parsedURLs = urls.map(s => new URL(s));

Просмотреть файл

@ -17,12 +17,11 @@
import * as dom from './dom';
import * as frames from './frames';
import { assert, helper, debugLogger } from './helper';
import * as input from './input';
import * as js from './javascript';
import * as network from './network';
import { Screenshotter } from './screenshotter';
import { TimeoutSettings } from './timeoutSettings';
import { TimeoutSettings } from './utils/timeoutSettings';
import * as types from './types';
import { BrowserContext } from './browserContext';
import { ConsoleMessage } from './console';
@ -30,6 +29,8 @@ import * as accessibility from './accessibility';
import { EventEmitter } from 'events';
import { FileChooser } from './fileChooser';
import { Progress, runAbortableTask } from './progress';
import { assert, isError } from './utils/utils';
import { debugLogger } from './utils/debugLogger';
export interface PageDelegate {
readonly rawMouse: input.RawMouse;
@ -462,7 +463,7 @@ export class PageBinding {
const result = await binding!.playwrightFunction({ frame: context.frame, page, context: page._browserContext }, ...args);
context.evaluateInternal(deliverResult, { name, seq, result }).catch(e => debugLogger.log('error', e));
} catch (error) {
if (helper.isError(error))
if (isError(error))
context.evaluateInternal(deliverError, { name, seq, message: error.message, stack: error.stack }).catch(e => debugLogger.log('error', e));
else
context.evaluateInternal(deliverErrorValue, { name, seq, error }).catch(e => debugLogger.log('error', e));

Просмотреть файл

@ -15,8 +15,9 @@
*/
import { TimeoutError } from './utils/errors';
import { assert, debugLogger, LogName } from './helper';
import { assert } from './utils/utils';
import { rewriteErrorMessage } from './utils/stackTrace';
import { debugLogger, LogName } from './utils/debugLogger';
export interface Progress {
readonly aborted: Promise<void>;

Просмотреть файл

@ -15,11 +15,10 @@
*/
import { TimeoutError } from '../utils/errors';
import { helper } from '../helper';
import { SerializedError, SerializedValue } from './channels';
export function serializeError(e: any): SerializedError {
if (helper.isError(e))
if (isError(e))
return { error: { message: e.message, stack: e.stack, name: e.name } };
return { value: serializeValue(e, value => ({ fallThrough: value }), new Set()) };
}

Просмотреть файл

@ -14,12 +14,12 @@
* limitations under the License.
*/
import { helper } from '../helper';
import { makeWaitForNextTask } from '../utils/utils';
export class Transport {
private _pipeWrite: NodeJS.WritableStream;
private _data = Buffer.from([]);
private _waitForNextTask = helper.makeWaitForNextTask();
private _waitForNextTask = makeWaitForNextTask();
private _closed = false;
private _bytesLeft = 0;

Просмотреть файл

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { isUnderTest } from '../helper';
import { isUnderTest } from '../utils/utils';
export class ValidationError extends Error {}
export type Validator = (arg: any, path: string) => any;

Просмотреть файл

@ -20,9 +20,9 @@ import { Page } from './page';
import { ChannelOwner } from './channelOwner';
import { Events } from './events';
import { BrowserType } from './browserType';
import { headersObjectToArray } from '../../converters';
import { BrowserContextOptions } from './types';
import { validateHeaders } from './network';
import { headersObjectToArray } from '../../utils/utils';
export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
readonly _contexts = new Set<BrowserContext>();

Просмотреть файл

@ -20,13 +20,13 @@ import { Page, BindingCall } from './page';
import * as network from './network';
import { BrowserContextChannel, BrowserContextInitializer } from '../../protocol/channels';
import { ChannelOwner } from './channelOwner';
import { isUnderTest, deprecate, evaluationScript, urlMatches } from './clientHelper';
import { deprecate, evaluationScript, urlMatches } from './clientHelper';
import { Browser } from './browser';
import { Events } from './events';
import { TimeoutSettings } from '../../timeoutSettings';
import { TimeoutSettings } from '../../utils/timeoutSettings';
import { Waiter } from './waiter';
import { headersObjectToArray } from '../../converters';
import { URLMatch, Headers, WaitForEventOptions } from './types';
import { isUnderTest, headersObjectToArray } from '../../utils/utils';
export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserContextInitializer> {
_pages = new Set<Page>();

Просмотреть файл

@ -18,17 +18,16 @@ import { BrowserTypeChannel, BrowserTypeInitializer, BrowserTypeLaunchParams, Br
import { Browser } from './browser';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { headersObjectToArray } from '../../converters';
import { assert, helper } from '../../helper';
import { LaunchOptions, LaunchServerOptions, ConnectOptions, LaunchPersistentContextOptions } from './types';
import * as WebSocket from 'ws';
import { Connection } from './connection';
import { serializeError } from '../../protocol/serializers';
import { Events } from './events';
import { TimeoutSettings } from '../../timeoutSettings';
import { TimeoutSettings } from '../../utils/timeoutSettings';
import { ChildProcess } from 'child_process';
import { envObjectToArray } from './clientHelper';
import { validateHeaders } from './network';
import { assert, makeWaitForNextTask, headersObjectToArray } from '../../utils/utils';
export interface BrowserServerLauncher {
launchServer(options?: LaunchServerOptions): Promise<BrowserServer>;
@ -122,7 +121,7 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni
// The 'ws' module in node sometimes sends us multiple messages in a single task.
const waitForNextTask = options.slowMo
? (cb: () => any) => setTimeout(cb, options.slowMo)
: helper.makeWaitForNextTask();
: makeWaitForNextTask();
connection.onmessage = message => {
if (ws.readyState !== WebSocket.OPEN) {
setTimeout(() => {

Просмотреть файл

@ -18,7 +18,7 @@ import { EventEmitter } from 'events';
import type { Channel } from '../../protocol/channels';
import type { Connection } from './connection';
import type { LoggerSink } from './types';
import { debugLogger } from '../../helper';
import { debugLogger } from '../../utils/debugLogger';
export abstract class ChannelOwner<T extends Channel = Channel, Initializer = {}> extends EventEmitter {
private _connection: Connection;

Просмотреть файл

@ -15,10 +15,10 @@
* limitations under the License.
*/
import { isUnderTest as commonIsUnderTest, helper } from '../../helper';
import * as types from './types';
import * as fs from 'fs';
import * as util from 'util';
import { isString, isRegExp } from '../../utils/utils';
const deprecatedHits = new Set();
export function deprecate(methodName: string, message: string) {
@ -28,10 +28,6 @@ export function deprecate(methodName: string, message: string) {
console.warn(message);
}
export function isUnderTest() {
return commonIsUnderTest();
}
export function envObjectToArray(env: types.Env): { name: string, value: string }[] {
const result: { name: string, value: string }[] = [];
for (const name in env) {
@ -49,7 +45,7 @@ export async function evaluationScript(fun: Function | string | { path?: string,
}
if (arg !== undefined)
throw new Error('Cannot evaluate a string with arguments');
if (helper.isString(fun))
if (isString(fun))
return fun;
if (fun.content !== undefined)
return fun.content;
@ -65,9 +61,9 @@ export async function evaluationScript(fun: Function | string | { path?: string,
export function urlMatches(urlString: string, match: types.URLMatch | undefined): boolean {
if (match === undefined || match === '')
return true;
if (helper.isString(match))
if (isString(match))
match = globToRegex(match);
if (helper.isRegExp(match))
if (isRegExp(match))
return match.test(urlString);
if (typeof match === 'string' && match === urlString)
return true;

Просмотреть файл

@ -39,7 +39,7 @@ import { Stream } from './stream';
import { createScheme, Validator, ValidationError } from '../../protocol/validator';
import { WebKitBrowser } from './webkitBrowser';
import { FirefoxBrowser } from './firefoxBrowser';
import { debugLogger } from '../../helper';
import { debugLogger } from '../../utils/debugLogger';
class Root extends ChannelOwner<Channel, {}> {
constructor(connection: Connection) {

Просмотреть файл

@ -19,7 +19,7 @@ import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { Page } from './page';
import { serializeArgument, FuncOn, parseResult, SmartHandle, JSHandle } from './jsHandle';
import { TimeoutSettings } from '../../timeoutSettings';
import { TimeoutSettings } from '../../utils/timeoutSettings';
import { Waiter } from './waiter';
import { Events } from './events';
import { WaitForEventOptions, Env, LoggerSink } from './types';

Просмотреть файл

@ -18,12 +18,12 @@ import { ElementHandleChannel, JSHandleInitializer, ElementHandleScrollIntoViewI
import { Frame } from './frame';
import { FuncOn, JSHandle, serializeArgument, parseResult } from './jsHandle';
import { ChannelOwner } from './channelOwner';
import { helper, assert, mkdirIfNeeded } from '../../helper';
import { SelectOption, FilePayload, Rect, SelectOptionOptions } from './types';
import * as fs from 'fs';
import * as mime from 'mime';
import * as path from 'path';
import * as util from 'util';
import { assert, isString, mkdirIfNeeded } from '../../utils/utils';
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
@ -246,7 +246,7 @@ export function convertSelectOptionValues(values: string | ElementHandle | Selec
assert(values[i] !== null, `options[${i}]: expected object, got null`);
if (values[0] instanceof ElementHandle)
return { elements: (values as ElementHandle[]).map((v: ElementHandle) => v._elementChannel) };
if (helper.isString(values[0]))
if (isString(values[0]))
return { options: (values as string[]).map(value => ({ value })) };
return { options: values as SelectOption[] };
}

Просмотреть файл

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { assert } from '../../helper';
import { assert } from '../../utils/utils';
import { FrameChannel, FrameInitializer, FrameNavigatedEvent, FrameGotoOptions, FrameWaitForSelectorOptions, FrameDispatchEventOptions, FrameSetContentOptions, FrameClickOptions, FrameDblclickOptions, FrameFillOptions, FrameFocusOptions, FrameTextContentOptions, FrameInnerTextOptions, FrameInnerHTMLOptions, FrameGetAttributeOptions, FrameHoverOptions, FrameSetInputFilesOptions, FrameTypeOptions, FramePressOptions, FrameCheckOptions, FrameUncheckOptions } from '../../protocol/channels';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';

Просмотреть файл

@ -18,12 +18,11 @@ import { URLSearchParams } from 'url';
import { RequestChannel, ResponseChannel, RouteChannel, RequestInitializer, ResponseInitializer, RouteInitializer } from '../../protocol/channels';
import { ChannelOwner } from './channelOwner';
import { Frame } from './frame';
import { headersArrayToObject, headersObjectToArray } from '../../converters';
import { Headers } from './types';
import * as fs from 'fs';
import * as mime from 'mime';
import * as util from 'util';
import { helper } from '../../helper';
import { isString, headersObjectToArray, headersArrayToObject } from '../../utils/utils';
export type NetworkCookie = {
name: string,
@ -175,7 +174,7 @@ export class Route extends ChannelOwner<RouteChannel, RouteInitializer> {
body = buffer.toString('base64');
isBase64 = true;
length = buffer.length;
} else if (helper.isString(response.body)) {
} else if (isString(response.body)) {
body = response.body;
isBase64 = false;
length = Buffer.byteLength(body);
@ -204,7 +203,7 @@ export class Route extends ChannelOwner<RouteChannel, RouteInitializer> {
}
async continue(overrides: { method?: string, headers?: Headers, postData?: string | Buffer } = {}) {
const postDataBuffer = helper.isString(overrides.postData) ? Buffer.from(overrides.postData, 'utf8') : overrides.postData;
const postDataBuffer = isString(overrides.postData) ? Buffer.from(overrides.postData, 'utf8') : overrides.postData;
await this._channel.continue({
method: overrides.method,
headers: overrides.headers ? headersObjectToArray(overrides.headers) : undefined,
@ -284,7 +283,7 @@ export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer>
export function validateHeaders(headers: Headers) {
for (const key of Object.keys(headers)) {
const value = headers[key];
if (!Object.is(value, undefined) && !helper.isString(value))
if (!Object.is(value, undefined) && !isString(value))
throw new Error(`Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
}
}

Просмотреть файл

@ -16,11 +16,10 @@
*/
import { Events } from './events';
import { assert, helper, Listener, mkdirIfNeeded } from '../../helper';
import { TimeoutSettings } from '../../timeoutSettings';
import { assert } from '../../utils/utils';
import { TimeoutSettings } from '../../utils/timeoutSettings';
import { BindingCallChannel, BindingCallInitializer, PageChannel, PageInitializer, PagePdfParams, FrameWaitForSelectorOptions, FrameDispatchEventOptions, FrameSetContentOptions, FrameGotoOptions, PageReloadOptions, PageGoBackOptions, PageGoForwardOptions, PageScreenshotOptions, FrameClickOptions, FrameDblclickOptions, FrameFillOptions, FrameFocusOptions, FrameTextContentOptions, FrameInnerTextOptions, FrameInnerHTMLOptions, FrameGetAttributeOptions, FrameHoverOptions, FrameSetInputFilesOptions, FrameTypeOptions, FramePressOptions, FrameCheckOptions, FrameUncheckOptions } from '../../protocol/channels';
import { parseError, serializeError } from '../../protocol/serializers';
import { headersObjectToArray } from '../../converters';
import { Accessibility } from './accessibility';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
@ -42,6 +41,7 @@ import * as fs from 'fs';
import * as util from 'util';
import { Size, URLMatch, Headers, LifecycleEvent, WaitForEventOptions, SelectOption, SelectOptionOptions, FilePayload, WaitForFunctionOptions } from './types';
import { evaluationScript, urlMatches } from './clientHelper';
import { isString, isRegExp, isObject, mkdirIfNeeded, headersObjectToArray } from '../../utils/utils';
type PDFOptions = Omit<PagePdfParams, 'width' | 'height' | 'margin'> & {
width?: string | number,
@ -54,6 +54,7 @@ type PDFOptions = Omit<PagePdfParams, 'width' | 'height' | 'margin'> & {
},
path?: string,
};
type Listener = (...args: any[]) => void;
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
@ -197,8 +198,8 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
}
frame(options: string | { name?: string, url?: URLMatch }): Frame | null {
const name = helper.isString(options) ? options : options.name;
const url = helper.isObject(options) ? options.url : undefined;
const name = isString(options) ? options : options.name;
const url = isObject(options) ? options.url : undefined;
assert(name || url, 'Either name or url matcher should be specified');
return this.frames().find(f => {
if (name)
@ -330,7 +331,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
async waitForRequest(urlOrPredicate: string | RegExp | ((r: Request) => boolean), options: { timeout?: number } = {}): Promise<Request> {
const predicate = (request: Request) => {
if (helper.isString(urlOrPredicate) || helper.isRegExp(urlOrPredicate))
if (isString(urlOrPredicate) || isRegExp(urlOrPredicate))
return urlMatches(request.url(), urlOrPredicate);
return urlOrPredicate(request);
};
@ -339,7 +340,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
async waitForResponse(urlOrPredicate: string | RegExp | ((r: Response) => boolean), options: { timeout?: number } = {}): Promise<Response> {
const predicate = (response: Response) => {
if (helper.isString(urlOrPredicate) || helper.isRegExp(urlOrPredicate))
if (isString(urlOrPredicate) || isRegExp(urlOrPredicate))
return urlMatches(response.url(), urlOrPredicate);
return urlOrPredicate(response);
};

Просмотреть файл

@ -19,8 +19,8 @@ import type { Playwright as PlaywrightImpl } from '../server/playwright';
import type { Playwright as PlaywrightAPI } from './client/playwright';
import { PlaywrightDispatcher } from './server/playwrightDispatcher';
import { Connection } from './client/connection';
import { isUnderTest } from '../helper';
import { BrowserServerLauncherImpl } from './browserServerImpl';
import { isUnderTest } from '../utils/utils';
export function setupInProcess(playwright: PlaywrightImpl): PlaywrightAPI {
const clientConnection = new Connection();

Просмотреть файл

@ -15,10 +15,11 @@
*/
import { EventEmitter } from 'events';
import { helper, debugAssert, assert } from '../../helper';
import { helper } from '../../helper';
import * as channels from '../../protocol/channels';
import { serializeError } from '../../protocol/serializers';
import { createScheme, Validator, ValidationError } from '../../protocol/validator';
import { assert, debugAssert } from '../../utils/utils';
export const dispatcherSymbol = Symbol('dispatcher');

Просмотреть файл

@ -173,11 +173,7 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameInitializer
}
async waitForFunction(params: channels.FrameWaitForFunctionParams): Promise<channels.FrameWaitForFunctionResult> {
const options = {
...params,
polling: params.pollingInterval === undefined ? 'raf' as const : params.pollingInterval
};
return { handle: createHandle(this._scope, await this._frame._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), options)) };
return { handle: createHandle(this._scope, await this._frame._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), params)) };
}
async title(): Promise<channels.FrameTitleResult> {

Просмотреть файл

@ -16,11 +16,12 @@
*/
import * as dom from './dom';
import { assert, helper } from './helper';
import { helper } from './helper';
import { Page } from './page';
import * as types from './types';
import { rewriteErrorMessage } from './utils/stackTrace';
import { Progress } from './progress';
import { assert } from './utils/utils';
export class Screenshotter {
private _queue = new TaskQueue();

Просмотреть файл

@ -16,7 +16,6 @@
import * as dom from './dom';
import * as frames from './frames';
import { helper, assert } from './helper';
import * as js from './javascript';
import * as types from './types';
import { ParsedSelector, parseSelector } from './common/selectorParser';
@ -116,7 +115,6 @@ export class Selectors {
}
_parseSelector(selector: string): SelectorInfo {
assert(helper.isString(selector), `selector must be a string`);
const parsed = parseSelector(selector);
for (const {name} of parsed.parts) {
if (!this._builtinEngines.has(name) && !this._engines.has(name))

Просмотреть файл

@ -22,13 +22,13 @@ import { BrowserContext, verifyProxySettings, validateBrowserContextOptions } fr
import * as browserPaths from '../install/browserPaths';
import { ConnectionTransport, WebSocketTransport } from '../transport';
import { BrowserOptions, Browser, BrowserProcess } from '../browser';
import { assert, helper } from '../helper';
import { launchProcess, Env, waitForLine, envArrayToObject } from './processLauncher';
import { PipeTransport } from './pipeTransport';
import { Progress, runAbortableTask } from '../progress';
import * as types from '../types';
import { TimeoutSettings } from '../timeoutSettings';
import { TimeoutSettings } from '../utils/timeoutSettings';
import { validateHostRequirements } from './validateDependencies';
import { assert, isDebugMode } from '../utils/utils';
export interface BrowserType {
executablePath(): string;
@ -223,7 +223,7 @@ function copyTestHooks(from: object, to: object) {
}
function validateLaunchOptions<Options extends types.LaunchOptions>(options: Options): Options {
const { devtools = false, headless = !helper.isDebugMode() && !devtools } = options;
const { devtools = false, headless = !isDebugMode() && !devtools } = options;
return { ...options, devtools, headless };
}

Просмотреть файл

@ -17,7 +17,6 @@
import * as path from 'path';
import * as os from 'os';
import { getFromENV, helper } from '../helper';
import { CRBrowser } from '../chromium/crBrowser';
import { Env } from './processLauncher';
import { kBrowserCloseMessageId } from '../chromium/crConnection';
@ -28,6 +27,7 @@ import { BrowserDescriptor } from '../install/browserPaths';
import { CRDevTools } from '../chromium/crDevTools';
import { BrowserOptions } from '../browser';
import * as types from '../types';
import { isDebugMode, getFromENV } from '../utils/utils';
export class Chromium extends BrowserTypeBase {
private _devtools: CRDevTools | undefined;
@ -43,7 +43,7 @@ export class Chromium extends BrowserTypeBase {
super(packagePath, browser, debugPort ? { webSocketRegex: /^DevTools listening on (ws:\/\/.*)$/, stream: 'stderr' } : null);
this._debugPort = debugPort;
if (helper.isDebugMode())
if (isDebugMode())
this._devtools = this._createDevTools();
}

Просмотреть файл

@ -20,7 +20,7 @@ import { CRConnection, CRSession } from '../chromium/crConnection';
import { CRExecutionContext } from '../chromium/crExecutionContext';
import * as js from '../javascript';
import { Page } from '../page';
import { TimeoutSettings } from '../timeoutSettings';
import { TimeoutSettings } from '../utils/timeoutSettings';
import { WebSocketTransport } from '../transport';
import * as types from '../types';
import { launchProcess, waitForLine, envArrayToObject } from './processLauncher';

Просмотреть файл

@ -15,14 +15,16 @@
* limitations under the License.
*/
import { helper, RegisteredListener, debugLogger } from '../helper';
import { helper, RegisteredListener } from '../helper';
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import { makeWaitForNextTask } from '../utils/utils';
import { debugLogger } from '../utils/debugLogger';
export class PipeTransport implements ConnectionTransport {
private _pipeWrite: NodeJS.WritableStream;
private _pendingMessage = '';
private _eventListeners: RegisteredListener[];
private _waitForNextTask = helper.makeWaitForNextTask();
private _waitForNextTask = makeWaitForNextTask();
private _closed = false;
onmessage?: (message: ProtocolResponse) => void;

Просмотреть файл

@ -19,9 +19,10 @@ import * as childProcess from 'child_process';
import * as readline from 'readline';
import * as removeFolder from 'rimraf';
import * as stream from 'stream';
import { helper, isUnderTest } from '../helper';
import { helper } from '../helper';
import { Progress } from '../progress';
import * as types from '../types';
import { isUnderTest } from '../utils/utils';
export type Env = {[key: string]: string | number | boolean | undefined};

Просмотреть файл

@ -16,8 +16,8 @@
*/
import * as WebSocket from 'ws';
import { helper } from './helper';
import { Progress } from './progress';
import { makeWaitForNextTask } from './utils/utils';
export type ProtocolRequest = {
id: number;
@ -85,7 +85,7 @@ export class WebSocketTransport implements ConnectionTransport {
// In Web, all IO callbacks (e.g. WebSocket callbacks)
// are dispatched into separate tasks, so there's no need
// to do anything extra.
const messageWrap: (cb: () => void) => void = helper.makeWaitForNextTask();
const messageWrap: (cb: () => void) => void = makeWaitForNextTask();
this._ws.addEventListener('message', event => {
messageWrap(() => {

Просмотреть файл

@ -26,8 +26,7 @@ export type TimeoutOptions = { timeout?: number };
export type WaitForElementOptions = TimeoutOptions & { state?: 'attached' | 'detached' | 'visible' | 'hidden' };
export type Polling = 'raf' | number;
export type WaitForFunctionOptions = TimeoutOptions & { polling?: Polling };
export type WaitForFunctionOptions = TimeoutOptions & { pollingInterval?: number };
export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle';
export const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle']);

63
src/utils/debugLogger.ts Normal file
Просмотреть файл

@ -0,0 +1,63 @@
/**
* 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 debug from 'debug';
import * as fs from 'fs';
const debugLoggerColorMap = {
'api': 45, // cyan
'protocol': 34, // green
'browser': 0, // reset
'error': 160, // red,
'channel:command': 33, // blue
'channel:response': 202, // orange
'channel:event': 207, // magenta
};
export type LogName = keyof typeof debugLoggerColorMap;
class DebugLogger {
private _debuggers = new Map<string, debug.IDebugger>();
constructor() {
if (process.env.DEBUG_FILE) {
const ansiRegex = new RegExp([
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
].join('|'), 'g');
const stream = fs.createWriteStream(process.env.DEBUG_FILE);
(debug as any).log = (data: string) => {
stream.write(data.replace(ansiRegex, ''));
stream.write('\n');
};
}
}
log(name: LogName, message: string | Error | object) {
let cachedDebugger = this._debuggers.get(name);
if (!cachedDebugger) {
cachedDebugger = debug(`pw:${name}`);
this._debuggers.set(name, cachedDebugger);
(cachedDebugger as any).color = debugLoggerColorMap[name];
}
cachedDebugger(message);
}
isEnabled(name: LogName) {
return debug.enabled(`pw:${name}`);
}
}
export const debugLogger = new DebugLogger();

Просмотреть файл

@ -17,7 +17,7 @@
import * as fs from 'fs';
import * as util from 'util';
import { getCallerFilePath } from './stackTrace';
import { helper } from '../helper';
import { isDebugMode } from './utils';
type Position = {
line: number;
@ -37,7 +37,7 @@ export function ensureSourceUrl(expression: string): string {
}
export async function generateSourceMapUrl(functionText: string, generatedText: string): Promise<string> {
if (!helper.isDebugMode())
if (!isDebugMode())
return generateSourceUrl();
const sourceMapUrl = await innerGenerateSourceMapUrl(functionText, generatedText);
return sourceMapUrl || generateSourceUrl();

Просмотреть файл

@ -15,10 +15,9 @@
* limitations under the License.
*/
import { TimeoutOptions } from './types';
import { helper } from './helper';
import { isDebugMode } from './utils';
const DEFAULT_TIMEOUT = helper.isDebugMode() ? 0 : 30000;
const DEFAULT_TIMEOUT = isDebugMode() ? 0 : 30000;
export class TimeoutSettings {
private _parent: TimeoutSettings | undefined;
@ -37,7 +36,7 @@ export class TimeoutSettings {
this._defaultNavigationTimeout = timeout;
}
navigationTimeout(options: TimeoutOptions): number {
navigationTimeout(options: { timeout?: number }): number {
if (typeof options.timeout === 'number')
return options.timeout;
if (this._defaultNavigationTimeout !== null)
@ -49,7 +48,7 @@ export class TimeoutSettings {
return DEFAULT_TIMEOUT;
}
timeout(options: TimeoutOptions): number {
timeout(options: { timeout?: number }): number {
if (typeof options.timeout === 'number')
return options.timeout;
if (this._defaultTimeout !== null)
@ -59,7 +58,7 @@ export class TimeoutSettings {
return DEFAULT_TIMEOUT;
}
static timeout(options: TimeoutOptions): number {
static timeout(options: { timeout?: number }): number {
if (typeof options.timeout === 'number')
return options.timeout;
return DEFAULT_TIMEOUT;

126
src/utils/utils.ts Normal file
Просмотреть файл

@ -0,0 +1,126 @@
/**
* 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 path from 'path';
import * as fs from 'fs';
import * as util from 'util';
const mkdirAsync = util.promisify(fs.mkdir.bind(fs));
// See https://joel.tools/microtasks/
export function makeWaitForNextTask() {
if (parseInt(process.versions.node, 10) >= 11)
return setImmediate;
// Unlike Node 11, Node 10 and less have a bug with Task and MicroTask execution order:
// - https://github.com/nodejs/node/issues/22257
//
// So we can't simply run setImmediate to dispatch code in a following task.
// However, we can run setImmediate from-inside setImmediate to make sure we're getting
// in the following task.
let spinning = false;
const callbacks: (() => void)[] = [];
const loop = () => {
const callback = callbacks.shift();
if (!callback) {
spinning = false;
return;
}
setImmediate(loop);
// Make sure to call callback() as the last thing since it's
// untrusted code that might throw.
callback();
};
return (callback: () => void) => {
callbacks.push(callback);
if (!spinning) {
spinning = true;
setImmediate(loop);
}
};
}
export function assert(value: any, message?: string): asserts value {
if (!value)
throw new Error(message);
}
export function debugAssert(value: any, message?: string): asserts value {
if (isUnderTest() && !value)
throw new Error(message);
}
export function isString(obj: any): obj is string {
return typeof obj === 'string' || obj instanceof String;
}
export function isRegExp(obj: any): obj is RegExp {
return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
}
export function isObject(obj: any): obj is NonNullable<object> {
return typeof obj === 'object' && obj !== null;
}
export function isError(obj: any): obj is Error {
return obj instanceof Error || (obj && obj.__proto__ && obj.__proto__.name === 'Error');
}
const isInDebugMode = !!getFromENV('PWDEBUG');
export function isDebugMode(): boolean {
return isInDebugMode;
}
let _isUnderTest = false;
export function setUnderTest() {
_isUnderTest = true;
}
export function isUnderTest(): boolean {
return _isUnderTest;
}
export function getFromENV(name: string) {
let value = process.env[name];
value = value || process.env[`npm_config_${name.toLowerCase()}`];
value = value || process.env[`npm_package_config_${name.toLowerCase()}`];
return value;
}
export async function mkdirIfNeeded(filePath: string) {
// This will harmlessly throw on windows if the dirname is the root directory.
await mkdirAsync(path.dirname(filePath), {recursive: true}).catch(() => {});
}
type HeadersArray = { name: string, value: string }[];
type HeadersObject = { [key: string]: string };
export function headersObjectToArray(headers: HeadersObject): HeadersArray {
const result: HeadersArray = [];
for (const name in headers) {
if (!Object.is(headers[name], undefined))
result.push({ name, value: headers[name] });
}
return result;
}
export function headersArrayToObject(headers: HeadersArray, lowerCase: boolean): HeadersObject {
const result: HeadersObject = {};
for (const { name, value } of headers)
result[lowerCase ? name.toLowerCase() : name] = value;
return result;
}

Просмотреть файл

@ -17,7 +17,8 @@
import { Browser, BrowserOptions } from '../browser';
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
import { helper, RegisteredListener, assert } from '../helper';
import { helper, RegisteredListener } from '../helper';
import { assert } from '../utils/utils';
import * as network from '../network';
import { Page, PageBinding } from '../page';
import * as path from 'path';

Просмотреть файл

@ -16,10 +16,11 @@
*/
import { EventEmitter } from 'events';
import { assert, debugLogger } from '../helper';
import { assert } from '../utils/utils';
import { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
import { Protocol } from './protocol';
import { rewriteErrorMessage } from '../utils/stackTrace';
import { debugLogger } from '../utils/debugLogger';
// WKPlaywright uses this special id to issue Browser.close command which we
// should ignore.

Просмотреть файл

@ -17,9 +17,9 @@
import * as input from '../input';
import * as types from '../types';
import { helper } from '../helper';
import { macEditingCommands } from '../macEditingCommands';
import { WKSession } from './wkConnection';
import { isString } from '../utils/utils';
function toModifiersMask(modifiers: Set<types.KeyboardModifier>): number {
// From Source/WebKit/Shared/WebEvent.h
@ -56,7 +56,7 @@ export class RawKeyboardImpl implements input.RawKeyboard {
parts.push(code);
const shortcut = parts.join('+');
let commands = macEditingCommands[shortcut];
if (helper.isString(commands))
if (isString(commands))
commands = [commands];
await this._pageProxySession.send('Input.dispatchKeyEvent', {
type: 'keyDown',

Просмотреть файл

@ -16,12 +16,11 @@
*/
import * as frames from '../frames';
import { assert } from '../helper';
import * as network from '../network';
import * as types from '../types';
import { Protocol } from './protocol';
import { WKSession } from './wkConnection';
import { headersArrayToObject, headersObjectToArray } from '../converters';
import { assert, headersObjectToArray, headersArrayToObject } from '../utils/utils';
const errorReasons: { [reason: string]: Protocol.Network.ResourceErrorType } = {
'aborted': 'Cancellation',

Просмотреть файл

@ -17,7 +17,7 @@
import { Screencast, BrowserContext } from '../browserContext';
import * as frames from '../frames';
import { helper, RegisteredListener, assert, debugAssert } from '../helper';
import { helper, RegisteredListener } from '../helper';
import * as dom from '../dom';
import * as network from '../network';
import { WKSession } from './wkConnection';
@ -37,7 +37,7 @@ import { selectors } from '../selectors';
import * as jpeg from 'jpeg-js';
import * as png from 'pngjs';
import { JSHandle } from '../javascript';
import { headersArrayToObject } from '../converters';
import { assert, debugAssert, headersArrayToObject } from '../utils/utils';
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';

Просмотреть файл

@ -16,8 +16,9 @@
import { WKSession } from './wkConnection';
import { WKPage } from './wkPage';
import { RegisteredListener, helper, assert } from '../helper';
import { RegisteredListener, helper } from '../helper';
import { Protocol } from './protocol';
import { assert } from '../utils/utils';
export class WKProvisionalPage {
readonly _session: WKSession;

2
test/fixtures/closeme.js поставляемый
Просмотреть файл

@ -8,7 +8,7 @@
}
const path = require('path');
const { setUnderTest } = require(path.join(playwrightPath, 'lib', 'helper'));
const { setUnderTest } = require(path.join(playwrightPath, 'lib', 'utils', 'utils'));
setUnderTest();
const playwright = require(path.join(playwrightPath, 'index'));

Просмотреть файл

@ -22,7 +22,7 @@ import { LaunchOptions, BrowserType, Browser, BrowserContext, Page, BrowserServe
import { TestServer } from '../utils/testserver';
import { Connection } from '../lib/rpc/client/connection';
import { Transport } from '../lib/protocol/transport';
import { setUnderTest } from '../lib/helper';
import { setUnderTest } from '../lib/utils/utils';
import { installCoverageHooks } from './coverage';
import { parameters, registerFixture, registerWorkerFixture } from '../test-runner';

Просмотреть файл

@ -19,6 +19,13 @@ import { options } from './playwright.fixtures';
import socks from 'socksv5';
it('should throw for bad server value', async ({browserType, defaultBrowserOptions}) => {
const error = await browserType.launch({
...defaultBrowserOptions,
proxy: { server: 123 as any }
}).catch(e => e);
expect(error.message).toContain('proxy.server: expected string, got number');
});
it('should use proxy', async ({browserType, defaultBrowserOptions, server}) => {
server.setRoute('/target.html', async (req, res) => {

Просмотреть файл

@ -17,6 +17,11 @@
import './playwright.fixtures';
it('should throw for non-string selector', async({page}) => {
const error = await page.$(null).catch(e => e);
expect(error.message).toContain('selector: expected string, got object');
});
it('should query existing element with css selector', async({page, server}) => {
await page.setContent('<section>test</section>');
const element = await page.$('css=section');

Просмотреть файл

@ -50,17 +50,28 @@ async function checkDeps() {
}
function allowImport(from, to) {
const rpc = path.join('src', 'rpc');
if (!from.includes(rpc) && to.includes(rpc))
from = from.substring(from.indexOf('src' + path.sep)).replace(/\\/g, '/');
to = to.substring(to.indexOf('src' + path.sep)).replace(/\\/g, '/') + '.ts';
while (from.lastIndexOf('/') !== -1) {
from = from.substring(0, from.lastIndexOf('/'));
const allowed = DEPS.get(from + '/');
if (!allowed)
continue;
for (const prefix of allowed) {
if (to.startsWith(prefix))
return true;
}
return false;
const rpcClient = path.join('src', 'rpc', 'client');
const rpcServer = path.join('src', 'rpc', 'server');
if (from.includes(rpcClient) && to.includes(rpcServer))
return false;
// if (from.includes(rpcClient) && !to.includes(rpc))
// return false;
return true;
}
return false;
}
}
const DEPS = new Map([
['src/utils/', ['src/utils/']],
['src/protocol/', ['src/protocol/', 'src/utils/']],
['src/rpc/client/', ['src/rpc/client/', 'src/utils/', 'src/protocol/', 'src/chromium/protocol.ts']],
['src/', ['src/']], // Allow everything else for now.
]);
checkDeps();