mirror of
https://github.com/microsoft/playwright.git
synced 2026-09-14 14:08:10 +08:00
168 lines
6.3 KiB
TypeScript
168 lines
6.3 KiB
TypeScript
/**
|
|
* 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 { getMetainfo } from './protocolMetainfo';
|
|
import { asLocatorDescription } from './locatorGenerators';
|
|
|
|
import type { Language } from './locatorGenerators';
|
|
|
|
export type CallMetainfo = {
|
|
type: string;
|
|
method: string;
|
|
params?: Record<string, any>;
|
|
// Pre-rendered overrides, take precedence over the protocol templates.
|
|
title?: string;
|
|
subtitle?: string;
|
|
};
|
|
|
|
export function formatProtocolParam(params: Record<string, any> | undefined, alternatives: string, sdkLanguage?: Language): string | undefined {
|
|
return _formatProtocolParam(params, alternatives, sdkLanguage)?.replaceAll('\n', '\\n');
|
|
}
|
|
|
|
function _formatProtocolParam(params: Record<string, any> | undefined, alternatives: string, sdkLanguage?: Language): string | undefined {
|
|
if (!params)
|
|
return undefined;
|
|
|
|
for (const name of alternatives.split('|')) {
|
|
if (name === 'url') {
|
|
try {
|
|
const urlObject = new URL(params[name]);
|
|
if (urlObject.protocol === 'data:')
|
|
return urlObject.protocol;
|
|
if (['about:', 'chrome:', 'edge:'].includes(urlObject.protocol))
|
|
return params[name];
|
|
return urlObject.host + urlObject.pathname + urlObject.search;
|
|
} catch (error) {
|
|
if (params[name] !== undefined)
|
|
return params[name];
|
|
}
|
|
}
|
|
if (name === 'timeNumber' && params[name] !== undefined) {
|
|
// eslint-disable-next-line no-restricted-globals
|
|
return new Date(params[name]).toString();
|
|
}
|
|
|
|
const value = deepParam(params, name);
|
|
if (value === undefined)
|
|
continue;
|
|
if (name === 'selector' || name.endsWith('.selector'))
|
|
return asLocatorDescription(sdkLanguage ?? 'javascript', value);
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function deepParam(params: Record<string, any>, name: string): string | undefined {
|
|
const tokens = name.split('.');
|
|
let current = params;
|
|
for (const token of tokens) {
|
|
if (typeof current !== 'object' || current === null)
|
|
return undefined;
|
|
current = current[token];
|
|
}
|
|
if (current === undefined)
|
|
return undefined;
|
|
return String(current);
|
|
}
|
|
|
|
export function renderTitleForCall(metadata: CallMetainfo, sdkLanguage?: Language): string {
|
|
const titleFormat = metadata.title ?? getMetainfo(metadata)?.title ?? metadata.method;
|
|
return titleFormat.replace(/\{([^}]+)\}/g, (fullMatch, p1) => {
|
|
return formatProtocolParam(metadata.params, p1, sdkLanguage) ?? fullMatch;
|
|
});
|
|
}
|
|
|
|
export function renderSubtitleForCall(metadata: CallMetainfo, sdkLanguage?: Language): string | undefined {
|
|
const subtitleFormat = metadata.subtitle ?? getMetainfo(metadata)?.subtitle;
|
|
if (subtitleFormat === undefined)
|
|
return undefined;
|
|
let allParamsResolved = true;
|
|
const subtitle = subtitleFormat.replace(/\{([^}]+)\}/g, (fullMatch, p1) => {
|
|
const param = formatProtocolParam(metadata.params, p1, sdkLanguage);
|
|
if (param === undefined)
|
|
allParamsResolved = false;
|
|
return param ?? fullMatch;
|
|
});
|
|
return allParamsResolved ? subtitle : undefined;
|
|
}
|
|
|
|
export function renderFullTitleForCall(metadata: CallMetainfo, sdkLanguage?: Language): string {
|
|
const title = renderTitleForCall(metadata, sdkLanguage);
|
|
const subtitle = renderSubtitleForCall(metadata, sdkLanguage);
|
|
return subtitle ? `${title} ${subtitle}` : title;
|
|
}
|
|
|
|
export type ActionGroup = 'configuration' | 'route' | 'getter';
|
|
|
|
export function getActionGroup(metadata: { type: string, method: string }) {
|
|
return getMetainfo(metadata)?.group as undefined | ActionGroup;
|
|
}
|
|
|
|
const kMaxParamLength = 200;
|
|
|
|
// Curated per-call parameters, defined by the "renderParams" lists in protocol.yml. Only the
|
|
// arguments that say what the call actually did are reported, and only when they are
|
|
// bounded in size: page content, evaluated expressions, request bodies and the like are
|
|
// never reported, and neither are options that repeat their default on every call.
|
|
export function renderParamsForCall(metadata: CallMetainfo, sdkLanguage?: Language): Record<string, any> | undefined {
|
|
if (!metadata.params)
|
|
return undefined;
|
|
const result: Record<string, any> = {};
|
|
const locator = renderLocator(metadata.params.selector, sdkLanguage);
|
|
if (locator !== undefined)
|
|
result.locator = locator;
|
|
for (const entry of getMetainfo(metadata)?.renderParams ?? []) {
|
|
const { key, value } = renderParamEntry(metadata.params, entry, sdkLanguage);
|
|
if (value !== undefined)
|
|
result[key] = value;
|
|
}
|
|
return Object.keys(result).length ? result : undefined;
|
|
}
|
|
|
|
// Each entry is "[key=]path[:selector]": a dotted path into the call params, reported
|
|
// under the last path segment unless an explicit key is given, with selector values
|
|
// rendered as locators.
|
|
function renderParamEntry(params: Record<string, any>, entry: string, sdkLanguage?: Language): { key: string, value: any } {
|
|
const [spec, format] = entry.split(':');
|
|
const eqIndex = spec.indexOf('=');
|
|
const path = eqIndex === -1 ? spec : spec.slice(eqIndex + 1);
|
|
const key = eqIndex === -1 ? path.split('.').pop()! : spec.slice(0, eqIndex);
|
|
let value: any = params;
|
|
for (const token of path.split('.')) {
|
|
if (typeof value !== 'object' || value === null) {
|
|
value = undefined;
|
|
break;
|
|
}
|
|
value = value[token];
|
|
}
|
|
if (value === undefined)
|
|
return { key, value };
|
|
if (format === 'selector')
|
|
return { key, value: renderLocator(value, sdkLanguage) };
|
|
if (typeof value === 'string')
|
|
return { key, value: truncateParam(value) };
|
|
return { key, value };
|
|
}
|
|
|
|
function renderLocator(selector: any, sdkLanguage?: Language): string | undefined {
|
|
if (typeof selector !== 'string')
|
|
return undefined;
|
|
return truncateParam(asLocatorDescription(sdkLanguage ?? 'javascript', selector));
|
|
}
|
|
|
|
export function truncateParam(value: string): string {
|
|
return value.length > kMaxParamLength ? value.substring(0, kMaxParamLength) + '…' : value;
|
|
}
|