Merge branch 'main' into remove-screenshot-instead-of-snapshot
This commit is contained in:
commit
43ad9ff286
|
|
@ -9,9 +9,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "chromium-tip-of-tree",
|
"name": "chromium-tip-of-tree",
|
||||||
"revision": "1269",
|
"revision": "1271",
|
||||||
"installByDefault": false,
|
"installByDefault": false,
|
||||||
"browserVersion": "131.0.6778.0"
|
"browserVersion": "132.0.6791.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "firefox",
|
"name": "firefox",
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "webkit",
|
"name": "webkit",
|
||||||
"revision": "2094",
|
"revision": "2095",
|
||||||
"installByDefault": true,
|
"installByDefault": true,
|
||||||
"revisionOverrides": {
|
"revisionOverrides": {
|
||||||
"mac10.14": "1446",
|
"mac10.14": "1446",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Mode, Source } from '@recorder/recorderTypes';
|
import type { ElementInfo, Mode, Source } from '@recorder/recorderTypes';
|
||||||
import { gracefullyProcessExitDoNotHang } from '../utils/processLauncher';
|
import { gracefullyProcessExitDoNotHang } from '../utils/processLauncher';
|
||||||
import type { Browser } from './browser';
|
import type { Browser } from './browser';
|
||||||
import type { BrowserContext } from './browserContext';
|
import type { BrowserContext } from './browserContext';
|
||||||
|
|
@ -221,9 +221,9 @@ class InspectingRecorderApp extends EmptyRecorderApp {
|
||||||
this._debugController = debugController;
|
this._debugController = debugController;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async setSelector(selector: string): Promise<void> {
|
override async elementPicked(elementInfo: ElementInfo): Promise<void> {
|
||||||
const locator: string = asLocator(this._debugController._sdkLanguage, selector);
|
const locator: string = asLocator(this._debugController._sdkLanguage, elementInfo.selector);
|
||||||
this._debugController.emit(DebugController.Events.InspectRequested, { selector, locator });
|
this._debugController.emit(DebugController.Events.InspectRequested, { selector: elementInfo.selector, locator });
|
||||||
}
|
}
|
||||||
|
|
||||||
override async setSources(sources: Source[]): Promise<void> {
|
override async setSources(sources: Source[]): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export function generateAriaTree(rootElement: Element): AriaNode {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const element = node as Element;
|
const element = node as Element;
|
||||||
if (roleUtils.isElementIgnoredForAria(element))
|
if (roleUtils.isElementHiddenForAria(element))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const visible = isElementVisible(element);
|
const visible = isElementVisible(element);
|
||||||
|
|
@ -281,7 +281,7 @@ export function renderAriaTree(ariaNode: AriaNode, options?: { noText?: boolean
|
||||||
const visit = (ariaNode: AriaNode | string, indent: string) => {
|
const visit = (ariaNode: AriaNode | string, indent: string) => {
|
||||||
if (typeof ariaNode === 'string') {
|
if (typeof ariaNode === 'string') {
|
||||||
if (!options?.noText)
|
if (!options?.noText)
|
||||||
lines.push(indent + '- text: ' + escapeYamlString(ariaNode));
|
lines.push(indent + '- text: ' + quoteYamlString(ariaNode));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let line = `${indent}- ${ariaNode.role}`;
|
let line = `${indent}- ${ariaNode.role}`;
|
||||||
|
|
@ -308,7 +308,7 @@ export function renderAriaTree(ariaNode: AriaNode, options?: { noText?: boolean
|
||||||
const stringValue = !ariaNode.children.length || (ariaNode.children?.length === 1 && typeof ariaNode.children[0] === 'string');
|
const stringValue = !ariaNode.children.length || (ariaNode.children?.length === 1 && typeof ariaNode.children[0] === 'string');
|
||||||
if (stringValue) {
|
if (stringValue) {
|
||||||
if (!options?.noText && ariaNode.children.length)
|
if (!options?.noText && ariaNode.children.length)
|
||||||
line += ': ' + escapeYamlString(ariaNode.children?.[0] as string);
|
line += ': ' + quoteYamlString(ariaNode.children?.[0] as string);
|
||||||
lines.push(line);
|
lines.push(line);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -328,36 +328,10 @@ export function renderAriaTree(ariaNode: AriaNode, options?: { noText?: boolean
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeYamlString(str: string) {
|
function quoteYamlString(str: string) {
|
||||||
if (str === '')
|
return `"${str
|
||||||
return '""';
|
.replace(/\\/g, '\\\\')
|
||||||
|
.replace(/"/g, '\\"')
|
||||||
const needQuotes = (
|
.replace(/\n/g, '\\n')
|
||||||
// Starts or ends with whitespace
|
.replace(/\r/g, '\\r')}"`;
|
||||||
/^\s|\s$/.test(str) ||
|
|
||||||
// Contains control characters
|
|
||||||
/[\x00-\x1f]/.test(str) ||
|
|
||||||
// Contains special YAML characters that could cause parsing issues
|
|
||||||
/[\[\]{}&*!,|>%@`]/.test(str) ||
|
|
||||||
// Contains a colon followed by a space (could be interpreted as a key-value pair)
|
|
||||||
/:\s/.test(str) ||
|
|
||||||
// Is a YAML boolean or null value
|
|
||||||
/^(?:y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|null|Null|NULL|~)$/.test(str) ||
|
|
||||||
// Could be interpreted as a number
|
|
||||||
/^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/.test(str) ||
|
|
||||||
// Contains a newline character
|
|
||||||
/\n/.test(str) ||
|
|
||||||
// Starts with a special character
|
|
||||||
/^[\-?:,>|%@"`]/.test(str)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (needQuotes) {
|
|
||||||
return `"${str
|
|
||||||
.replace(/\\/g, '\\\\')
|
|
||||||
.replace(/"/g, '\\"')
|
|
||||||
.replace(/\n/g, '\\n')
|
|
||||||
.replace(/\r/g, '\\r')}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,11 @@ class ConsoleAPI {
|
||||||
inspect: (selector: string) => this._inspect(selector),
|
inspect: (selector: string) => this._inspect(selector),
|
||||||
selector: (element: Element) => this._selector(element),
|
selector: (element: Element) => this._selector(element),
|
||||||
generateLocator: (element: Element, language?: Language) => this._generateLocator(element, language),
|
generateLocator: (element: Element, language?: Language) => this._generateLocator(element, language),
|
||||||
ariaSnapshot: (element?: Element) => this._injectedScript.ariaSnapshot(element || this._injectedScript.document.body),
|
ariaSnapshot: (element?: Element) => {
|
||||||
|
const snapshot = this._injectedScript.ariaSnapshot(element || this._injectedScript.document.body);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(snapshot);
|
||||||
|
},
|
||||||
resume: () => this._resume(),
|
resume: () => this._resume(),
|
||||||
...new Locator(injectedScript, ''),
|
...new Locator(injectedScript, ''),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes';
|
import type { ElementInfo, Mode, OverlayState, UIState } from '@recorder/recorderTypes';
|
||||||
import type * as actions from '@recorder/actions';
|
import type * as actions from '@recorder/actions';
|
||||||
import type { InjectedScript } from '../injectedScript';
|
import type { InjectedScript } from '../injectedScript';
|
||||||
import { Recorder } from './recorder';
|
import { Recorder } from './recorder';
|
||||||
|
|
@ -24,7 +24,7 @@ interface Embedder {
|
||||||
__pw_recorderPerformAction(action: actions.PerformOnRecordAction): Promise<void>;
|
__pw_recorderPerformAction(action: actions.PerformOnRecordAction): Promise<void>;
|
||||||
__pw_recorderRecordAction(action: actions.Action): Promise<void>;
|
__pw_recorderRecordAction(action: actions.Action): Promise<void>;
|
||||||
__pw_recorderState(): Promise<UIState>;
|
__pw_recorderState(): Promise<UIState>;
|
||||||
__pw_recorderSetSelector(selector: string): Promise<void>;
|
__pw_recorderElementPicked(element: { selector: string, ariaSnapshot?: string }): Promise<void>;
|
||||||
__pw_recorderSetMode(mode: Mode): Promise<void>;
|
__pw_recorderSetMode(mode: Mode): Promise<void>;
|
||||||
__pw_recorderSetOverlayState(state: OverlayState): Promise<void>;
|
__pw_recorderSetOverlayState(state: OverlayState): Promise<void>;
|
||||||
__pw_refreshOverlay(): void;
|
__pw_refreshOverlay(): void;
|
||||||
|
|
@ -75,8 +75,8 @@ export class PollingRecorder implements RecorderDelegate {
|
||||||
await this._embedder.__pw_recorderRecordAction(action);
|
await this._embedder.__pw_recorderRecordAction(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSelector(selector: string): Promise<void> {
|
async elementPicked(elementInfo: ElementInfo): Promise<void> {
|
||||||
await this._embedder.__pw_recorderSetSelector(selector);
|
await this._embedder.__pw_recorderElementPicked(elementInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMode(mode: Mode): Promise<void> {
|
async setMode(mode: Mode): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import type * as actions from '@recorder/actions';
|
import type * as actions from '@recorder/actions';
|
||||||
import type { InjectedScript } from '../injectedScript';
|
import type { InjectedScript } from '../injectedScript';
|
||||||
import type { Point } from '../../../common/types';
|
import type { Point } from '../../../common/types';
|
||||||
import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes';
|
import type { ElementInfo, Mode, OverlayState, UIState } from '@recorder/recorderTypes';
|
||||||
import type { ElementText } from '../selectorUtils';
|
import type { ElementText } from '../selectorUtils';
|
||||||
import type { Highlight, HighlightOptions } from '../highlight';
|
import type { Highlight, HighlightOptions } from '../highlight';
|
||||||
import clipPaths from './clipPaths';
|
import clipPaths from './clipPaths';
|
||||||
|
|
@ -25,7 +25,7 @@ import clipPaths from './clipPaths';
|
||||||
export interface RecorderDelegate {
|
export interface RecorderDelegate {
|
||||||
performAction?(action: actions.PerformOnRecordAction): Promise<void>;
|
performAction?(action: actions.PerformOnRecordAction): Promise<void>;
|
||||||
recordAction?(action: actions.Action): Promise<void>;
|
recordAction?(action: actions.Action): Promise<void>;
|
||||||
setSelector?(selector: string): Promise<void>;
|
elementPicked?(elementInfo: ElementInfo): Promise<void>;
|
||||||
setMode?(mode: Mode): Promise<void>;
|
setMode?(mode: Mode): Promise<void>;
|
||||||
setOverlayState?(state: OverlayState): Promise<void>;
|
setOverlayState?(state: OverlayState): Promise<void>;
|
||||||
highlightUpdated?(): void;
|
highlightUpdated?(): void;
|
||||||
|
|
@ -85,7 +85,7 @@ class InspectTool implements RecorderTool {
|
||||||
if (event.button !== 0)
|
if (event.button !== 0)
|
||||||
return;
|
return;
|
||||||
if (this._hoveredModel?.selector)
|
if (this._hoveredModel?.selector)
|
||||||
this._commit(this._hoveredModel.selector);
|
this._commit(this._hoveredModel.selector, this._hoveredModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
onContextMenu(event: MouseEvent) {
|
onContextMenu(event: MouseEvent) {
|
||||||
|
|
@ -93,13 +93,14 @@ class InspectTool implements RecorderTool {
|
||||||
&& this._hoveredSelectors && this._hoveredSelectors.length > 1) {
|
&& this._hoveredSelectors && this._hoveredSelectors.length > 1) {
|
||||||
consumeEvent(event);
|
consumeEvent(event);
|
||||||
const selectors = this._hoveredSelectors;
|
const selectors = this._hoveredSelectors;
|
||||||
|
const hoveredModel = this._hoveredModel;
|
||||||
this._hoveredModel.tooltipFooter = undefined;
|
this._hoveredModel.tooltipFooter = undefined;
|
||||||
this._hoveredModel.tooltipList = selectors.map(selector => this._recorder.injectedScript.utils.asLocator(this._recorder.state.language, selector));
|
this._hoveredModel.tooltipList = selectors.map(selector => this._recorder.injectedScript.utils.asLocator(this._recorder.state.language, selector));
|
||||||
this._hoveredModel.tooltipListItemSelected = (index: number | undefined) => {
|
this._hoveredModel.tooltipListItemSelected = (index: number | undefined) => {
|
||||||
if (index === undefined)
|
if (index === undefined)
|
||||||
this._reset(true);
|
this._reset(true);
|
||||||
else
|
else
|
||||||
this._commit(selectors[index]);
|
this._commit(selectors[index], hoveredModel);
|
||||||
};
|
};
|
||||||
this._recorder.updateHighlight(this._hoveredModel, true);
|
this._recorder.updateHighlight(this._hoveredModel, true);
|
||||||
}
|
}
|
||||||
|
|
@ -181,7 +182,7 @@ class InspectTool implements RecorderTool {
|
||||||
this._reset(false);
|
this._reset(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _commit(selector: string) {
|
private _commit(selector: string, model: HighlightModel) {
|
||||||
if (this._assertVisibility) {
|
if (this._assertVisibility) {
|
||||||
this._recorder.recordAction({
|
this._recorder.recordAction({
|
||||||
name: 'assertVisible',
|
name: 'assertVisible',
|
||||||
|
|
@ -191,7 +192,7 @@ class InspectTool implements RecorderTool {
|
||||||
this._recorder.setMode('recording');
|
this._recorder.setMode('recording');
|
||||||
this._recorder.overlay?.flashToolSucceeded('assertingVisibility');
|
this._recorder.overlay?.flashToolSucceeded('assertingVisibility');
|
||||||
} else {
|
} else {
|
||||||
this._recorder.setSelector(selector);
|
this._recorder.elementPicked(selector, model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1314,8 +1315,9 @@ export class Recorder {
|
||||||
void this._delegate.setOverlayState?.(state);
|
void this._delegate.setOverlayState?.(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelector(selector: string) {
|
elementPicked(selector: string, model: HighlightModel) {
|
||||||
void this._delegate.setSelector?.(selector);
|
const ariaSnapshot = this.injectedScript.ariaSnapshot(model.elements[0]);
|
||||||
|
void this._delegate.elementPicked?.({ selector, ariaSnapshot });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import type { CallLog, CallLogStatus, EventData, Mode, OverlayState, Source, UIState } from '@recorder/recorderTypes';
|
import type { CallLog, CallLogStatus, ElementInfo, EventData, Mode, OverlayState, Source, UIState } from '@recorder/recorderTypes';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import type { Point } from '../common/types';
|
import type { Point } from '../common/types';
|
||||||
import * as consoleApiSource from '../generated/consoleApiSource';
|
import * as consoleApiSource from '../generated/consoleApiSource';
|
||||||
|
|
@ -168,9 +168,9 @@ export class Recorder implements InstrumentationListener, IRecorder {
|
||||||
return uiState;
|
return uiState;
|
||||||
});
|
});
|
||||||
|
|
||||||
await this._context.exposeBinding('__pw_recorderSetSelector', false, async ({ frame }, selector: string) => {
|
await this._context.exposeBinding('__pw_recorderElementPicked', false, async ({ frame }, elementInfo: ElementInfo) => {
|
||||||
const selectorChain = await generateFrameSelector(frame);
|
const selectorChain = await generateFrameSelector(frame);
|
||||||
await this._recorderApp?.setSelector(buildFullSelector(selectorChain, selector), true);
|
await this._recorderApp?.elementPicked({ selector: buildFullSelector(selectorChain, elementInfo.selector), ariaSnapshot: elementInfo.ariaSnapshot }, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
await this._context.exposeBinding('__pw_recorderSetMode', false, async ({ frame }, mode: Mode) => {
|
await this._context.exposeBinding('__pw_recorderSetMode', false, async ({ frame }, mode: Mode) => {
|
||||||
|
|
@ -256,12 +256,10 @@ export class Recorder implements InstrumentationListener, IRecorder {
|
||||||
this._currentCallsMetadata.set(metadata, sdkObject);
|
this._currentCallsMetadata.set(metadata, sdkObject);
|
||||||
this._updateUserSources();
|
this._updateUserSources();
|
||||||
this.updateCallLog([metadata]);
|
this.updateCallLog([metadata]);
|
||||||
if (isScreenshotCommand(metadata)) {
|
if (isScreenshotCommand(metadata))
|
||||||
this.hideHighlightedSelector();
|
this.hideHighlightedSelector();
|
||||||
} else if (metadata.params && metadata.params.selector) {
|
else if (metadata.params && metadata.params.selector)
|
||||||
this._highlightedSelector = metadata.params.selector;
|
this._highlightedSelector = metadata.params.selector;
|
||||||
this._recorderApp?.setSelector(this._highlightedSelector).catch(() => {});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import type { Page } from '../page';
|
||||||
import { ProgressController } from '../progress';
|
import { ProgressController } from '../progress';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { serverSideCallMetadata } from '../instrumentation';
|
import { serverSideCallMetadata } from '../instrumentation';
|
||||||
import type { CallLog, Mode, Source } from '@recorder/recorderTypes';
|
import type { CallLog, ElementInfo, Mode, Source } from '@recorder/recorderTypes';
|
||||||
import { isUnderTest } from '../../utils';
|
import { isUnderTest } from '../../utils';
|
||||||
import { mime } from '../../utilsBundle';
|
import { mime } from '../../utilsBundle';
|
||||||
import { syncLocalStorageWithSettings } from '../launchApp';
|
import { syncLocalStorageWithSettings } from '../launchApp';
|
||||||
|
|
@ -35,7 +35,7 @@ export class EmptyRecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
async setPaused(paused: boolean): Promise<void> {}
|
async setPaused(paused: boolean): Promise<void> {}
|
||||||
async setMode(mode: Mode): Promise<void> {}
|
async setMode(mode: Mode): Promise<void> {}
|
||||||
async setRunningFile(file: string | undefined): Promise<void> {}
|
async setRunningFile(file: string | undefined): Promise<void> {}
|
||||||
async setSelector(selector: string, userGesture?: boolean): Promise<void> {}
|
async elementPicked(elementInfo: ElementInfo, userGesture?: boolean): Promise<void> {}
|
||||||
async updateCallLogs(callLogs: CallLog[]): Promise<void> {}
|
async updateCallLogs(callLogs: CallLog[]): Promise<void> {}
|
||||||
async setSources(sources: Source[]): Promise<void> {}
|
async setSources(sources: Source[]): Promise<void> {}
|
||||||
async setActions(actions: actions.ActionInContext[], sources: Source[]): Promise<void> {}
|
async setActions(actions: actions.ActionInContext[], sources: Source[]): Promise<void> {}
|
||||||
|
|
@ -158,18 +158,12 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
async setActions(actions: actions.ActionInContext[], sources: Source[]): Promise<void> {
|
async setActions(actions: actions.ActionInContext[], sources: Source[]): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSelector(selector: string, userGesture?: boolean): Promise<void> {
|
async elementPicked(elementInfo: ElementInfo, userGesture?: boolean): Promise<void> {
|
||||||
if (userGesture) {
|
if (userGesture)
|
||||||
if (this._recorder?.mode() === 'inspecting') {
|
this._page.bringToFront();
|
||||||
this._recorder.setMode('standby');
|
await this._page.mainFrame().evaluateExpression(((param: { elementInfo: ElementInfo, userGesture?: boolean }) => {
|
||||||
this._page.bringToFront();
|
window.playwrightElementPicked(param.elementInfo, param.userGesture);
|
||||||
} else {
|
}).toString(), { isFunction: true }, { elementInfo, userGesture }).catch(() => {});
|
||||||
this._recorder?.setMode('recording');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await this._page.mainFrame().evaluateExpression(((data: { selector: string, userGesture?: boolean }) => {
|
|
||||||
window.playwrightSetSelector(data.selector, data.userGesture);
|
|
||||||
}).toString(), { isFunction: true }, { selector, userGesture }).catch(() => {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCallLogs(callLogs: CallLog[]): Promise<void> {
|
async updateCallLogs(callLogs: CallLog[]): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type * as actions from '@recorder/actions';
|
import type * as actions from '@recorder/actions';
|
||||||
import type { CallLog, Mode, Source } from '@recorder/recorderTypes';
|
import type { CallLog, Mode, Source, ElementInfo } from '@recorder/recorderTypes';
|
||||||
import type { EventEmitter } from 'events';
|
import type { EventEmitter } from 'events';
|
||||||
|
|
||||||
export interface IRecorder {
|
export interface IRecorder {
|
||||||
|
|
@ -29,7 +29,7 @@ export interface IRecorderApp extends EventEmitter {
|
||||||
setPaused(paused: boolean): Promise<void>;
|
setPaused(paused: boolean): Promise<void>;
|
||||||
setMode(mode: Mode): Promise<void>;
|
setMode(mode: Mode): Promise<void>;
|
||||||
setRunningFile(file: string | undefined): Promise<void>;
|
setRunningFile(file: string | undefined): Promise<void>;
|
||||||
setSelector(selector: string, userGesture?: boolean): Promise<void>;
|
elementPicked(elementInfo: ElementInfo, userGesture?: boolean): Promise<void>;
|
||||||
updateCallLogs(callLogs: CallLog[]): Promise<void>;
|
updateCallLogs(callLogs: CallLog[]): Promise<void>;
|
||||||
setSources(sources: Source[]): Promise<void>;
|
setSources(sources: Source[]): Promise<void>;
|
||||||
setActions(actions: actions.ActionInContext[], sources: Source[]): Promise<void>;
|
setActions(actions: actions.ActionInContext[], sources: Source[]): Promise<void>;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type { CallLog, Mode, Source } from '@recorder/recorderTypes';
|
import type { CallLog, ElementInfo, Mode, Source } from '@recorder/recorderTypes';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import type { IRecorder, IRecorderApp, IRecorderAppFactory } from './recorderFrontend';
|
import type { IRecorder, IRecorderApp, IRecorderAppFactory } from './recorderFrontend';
|
||||||
import { installRootRedirect, openTraceViewerApp, startTraceViewerServer } from '../trace/viewer/traceViewer';
|
import { installRootRedirect, openTraceViewerApp, startTraceViewerServer } from '../trace/viewer/traceViewer';
|
||||||
|
|
@ -70,8 +70,8 @@ export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp
|
||||||
this._transport.deliverEvent('setRunningFile', { file });
|
this._transport.deliverEvent('setRunningFile', { file });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSelector(selector: string, userGesture?: boolean): Promise<void> {
|
async elementPicked(elementInfo: ElementInfo, userGesture?: boolean): Promise<void> {
|
||||||
this._transport.deliverEvent('setSelector', { selector, userGesture });
|
this._transport.deliverEvent('elementPicked', { elementInfo, userGesture });
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCallLogs(callLogs: CallLog[]): Promise<void> {
|
async updateCallLogs(callLogs: CallLog[]): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,11 @@ const EXECUTABLE_PATHS = {
|
||||||
'mac': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
|
'mac': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
|
||||||
'win': ['chrome-win', 'chrome.exe'],
|
'win': ['chrome-win', 'chrome.exe'],
|
||||||
},
|
},
|
||||||
|
'chromium-headless-shell': {
|
||||||
|
'linux': ['chrome-linux', 'headless_shell'],
|
||||||
|
'mac': ['chrome-mac', 'headless_shell'],
|
||||||
|
'win': ['chrome-win', 'headless_shell.exe'],
|
||||||
|
},
|
||||||
'firefox': {
|
'firefox': {
|
||||||
'linux': ['firefox', 'firefox'],
|
'linux': ['firefox', 'firefox'],
|
||||||
'mac': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
|
'mac': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
|
||||||
|
|
@ -104,6 +109,35 @@ const DOWNLOAD_PATHS: Record<BrowserName | InternalTool, DownloadPaths> = {
|
||||||
'mac15-arm64': 'builds/chromium/%s/chromium-mac-arm64.zip',
|
'mac15-arm64': 'builds/chromium/%s/chromium-mac-arm64.zip',
|
||||||
'win64': 'builds/chromium/%s/chromium-win64.zip',
|
'win64': 'builds/chromium/%s/chromium-win64.zip',
|
||||||
},
|
},
|
||||||
|
'chromium-headless-shell': {
|
||||||
|
'<unknown>': undefined,
|
||||||
|
'ubuntu18.04-x64': undefined,
|
||||||
|
'ubuntu20.04-x64': 'builds/chromium/%s/chromium-headless-shell-linux.zip',
|
||||||
|
'ubuntu22.04-x64': 'builds/chromium/%s/chromium-headless-shell-linux.zip',
|
||||||
|
'ubuntu24.04-x64': 'builds/chromium/%s/chromium-headless-shell-linux.zip',
|
||||||
|
'ubuntu18.04-arm64': undefined,
|
||||||
|
'ubuntu20.04-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
|
||||||
|
'ubuntu22.04-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
|
||||||
|
'ubuntu24.04-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
|
||||||
|
'debian11-x64': 'builds/chromium/%s/chromium-headless-shell-linux.zip',
|
||||||
|
'debian11-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
|
||||||
|
'debian12-x64': 'builds/chromium/%s/chromium-headless-shell-linux.zip',
|
||||||
|
'debian12-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
|
||||||
|
'mac10.13': undefined,
|
||||||
|
'mac10.14': undefined,
|
||||||
|
'mac10.15': undefined,
|
||||||
|
'mac11': 'builds/chromium/%s/chromium-headless-shell-mac.zip',
|
||||||
|
'mac11-arm64': 'builds/chromium/%s/chromium-headless-shell-mac-arm64.zip',
|
||||||
|
'mac12': 'builds/chromium/%s/chromium-headless-shell-mac.zip',
|
||||||
|
'mac12-arm64': 'builds/chromium/%s/chromium-headless-shell-mac-arm64.zip',
|
||||||
|
'mac13': 'builds/chromium/%s/chromium-headless-shell-mac.zip',
|
||||||
|
'mac13-arm64': 'builds/chromium/%s/chromium-headless-shell-mac-arm64.zip',
|
||||||
|
'mac14': 'builds/chromium/%s/chromium-headless-shell-mac.zip',
|
||||||
|
'mac14-arm64': 'builds/chromium/%s/chromium-headless-shell-mac-arm64.zip',
|
||||||
|
'mac15': 'builds/chromium/%s/chromium-headless-shell-mac.zip',
|
||||||
|
'mac15-arm64': 'builds/chromium/%s/chromium-headless-shell-mac-arm64.zip',
|
||||||
|
'win64': 'builds/chromium/%s/chromium-headless-shell-win64.zip',
|
||||||
|
},
|
||||||
'chromium-tip-of-tree': {
|
'chromium-tip-of-tree': {
|
||||||
'<unknown>': undefined,
|
'<unknown>': undefined,
|
||||||
'ubuntu18.04-x64': undefined,
|
'ubuntu18.04-x64': undefined,
|
||||||
|
|
@ -343,7 +377,7 @@ type BrowsersJSONDescriptor = {
|
||||||
dir: string,
|
dir: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function readDescriptors(browsersJSON: BrowsersJSON) {
|
function readDescriptors(browsersJSON: BrowsersJSON): BrowsersJSONDescriptor[] {
|
||||||
return (browsersJSON['browsers']).map(obj => {
|
return (browsersJSON['browsers']).map(obj => {
|
||||||
const name = obj.name;
|
const name = obj.name;
|
||||||
const revisionOverride = (obj.revisionOverrides || {})[hostPlatform];
|
const revisionOverride = (obj.revisionOverrides || {})[hostPlatform];
|
||||||
|
|
@ -367,10 +401,10 @@ function readDescriptors(browsersJSON: BrowsersJSON) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BrowserName = 'chromium' | 'firefox' | 'webkit' | 'bidi';
|
export type BrowserName = 'chromium' | 'firefox' | 'webkit' | 'bidi';
|
||||||
type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-tip-of-tree' | 'android';
|
type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-tip-of-tree' | 'chromium-headless-shell' |'android';
|
||||||
type BidiChannel = 'bidi-firefox-stable' | 'bidi-firefox-beta' | 'bidi-firefox-nightly' | 'bidi-chrome-canary' | 'bidi-chrome-stable' | 'bidi-chromium';
|
type BidiChannel = 'bidi-firefox-stable' | 'bidi-firefox-beta' | 'bidi-firefox-nightly' | 'bidi-chrome-canary' | 'bidi-chrome-stable' | 'bidi-chromium';
|
||||||
type ChromiumChannel = 'chrome' | 'chrome-beta' | 'chrome-dev' | 'chrome-canary' | 'msedge' | 'msedge-beta' | 'msedge-dev' | 'msedge-canary';
|
type ChromiumChannel = 'chrome' | 'chrome-beta' | 'chrome-dev' | 'chrome-canary' | 'msedge' | 'msedge-beta' | 'msedge-dev' | 'msedge-canary';
|
||||||
const allDownloadable = ['chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-tip-of-tree'];
|
const allDownloadable = ['chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-tip-of-tree', 'chromium-headless-shell'];
|
||||||
|
|
||||||
export interface Executable {
|
export interface Executable {
|
||||||
type: 'browser' | 'tool' | 'channel';
|
type: 'browser' | 'tool' | 'channel';
|
||||||
|
|
@ -445,7 +479,7 @@ export class Registry {
|
||||||
executablePath: () => chromiumExecutable,
|
executablePath: () => chromiumExecutable,
|
||||||
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium', chromiumExecutable, chromium.installByDefault, sdkLanguage),
|
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium', chromiumExecutable, chromium.installByDefault, sdkLanguage),
|
||||||
installType: chromium.installByDefault ? 'download-by-default' : 'download-on-demand',
|
installType: chromium.installByDefault ? 'download-by-default' : 'download-on-demand',
|
||||||
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'chromium', chromium.dir, ['chrome-linux'], [], ['chrome-win']),
|
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, chromium.dir, ['chrome-linux'], [], ['chrome-win']),
|
||||||
downloadURLs: this._downloadURLs(chromium),
|
downloadURLs: this._downloadURLs(chromium),
|
||||||
browserVersion: chromium.browserVersion,
|
browserVersion: chromium.browserVersion,
|
||||||
_install: () => this._downloadExecutable(chromium, chromiumExecutable),
|
_install: () => this._downloadExecutable(chromium, chromiumExecutable),
|
||||||
|
|
@ -453,6 +487,30 @@ export class Registry {
|
||||||
_isHermeticInstallation: true,
|
_isHermeticInstallation: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const chromiumHeadlessShellDescriptor: BrowsersJSONDescriptor = {
|
||||||
|
name: 'chromium-headless-shell',
|
||||||
|
revision: chromium.revision,
|
||||||
|
browserVersion: chromium.browserVersion,
|
||||||
|
dir: chromium.dir.replace(/(.*)(-\d+)$/, '$1-headless-shell$2'),
|
||||||
|
installByDefault: false
|
||||||
|
};
|
||||||
|
const chromiumHeadlessShellExecutable = findExecutablePath(chromiumHeadlessShellDescriptor.dir, 'chromium-headless-shell');
|
||||||
|
this._executables.push({
|
||||||
|
type: 'tool',
|
||||||
|
name: 'chromium-headless-shell',
|
||||||
|
browserName: 'chromium',
|
||||||
|
directory: chromiumHeadlessShellDescriptor.dir,
|
||||||
|
executablePath: () => chromiumHeadlessShellExecutable,
|
||||||
|
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium-headless-shell', chromiumHeadlessShellExecutable, false, sdkLanguage),
|
||||||
|
installType: 'download-on-demand',
|
||||||
|
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, chromiumHeadlessShellDescriptor.dir, ['chrome-linux'], [], ['chrome-win']),
|
||||||
|
downloadURLs: this._downloadURLs(chromiumHeadlessShellDescriptor),
|
||||||
|
browserVersion: chromium.browserVersion,
|
||||||
|
_install: () => this._downloadExecutable(chromiumHeadlessShellDescriptor, chromiumHeadlessShellExecutable),
|
||||||
|
_dependencyGroup: 'chromium',
|
||||||
|
_isHermeticInstallation: true,
|
||||||
|
});
|
||||||
|
|
||||||
const chromiumTipOfTree = descriptors.find(d => d.name === 'chromium-tip-of-tree')!;
|
const chromiumTipOfTree = descriptors.find(d => d.name === 'chromium-tip-of-tree')!;
|
||||||
const chromiumTipOfTreeExecutable = findExecutablePath(chromiumTipOfTree.dir, 'chromium');
|
const chromiumTipOfTreeExecutable = findExecutablePath(chromiumTipOfTree.dir, 'chromium');
|
||||||
this._executables.push({
|
this._executables.push({
|
||||||
|
|
@ -463,7 +521,7 @@ export class Registry {
|
||||||
executablePath: () => chromiumTipOfTreeExecutable,
|
executablePath: () => chromiumTipOfTreeExecutable,
|
||||||
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium-tip-of-tree', chromiumTipOfTreeExecutable, chromiumTipOfTree.installByDefault, sdkLanguage),
|
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium-tip-of-tree', chromiumTipOfTreeExecutable, chromiumTipOfTree.installByDefault, sdkLanguage),
|
||||||
installType: chromiumTipOfTree.installByDefault ? 'download-by-default' : 'download-on-demand',
|
installType: chromiumTipOfTree.installByDefault ? 'download-by-default' : 'download-on-demand',
|
||||||
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'chromium', chromiumTipOfTree.dir, ['chrome-linux'], [], ['chrome-win']),
|
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, chromiumTipOfTree.dir, ['chrome-linux'], [], ['chrome-win']),
|
||||||
downloadURLs: this._downloadURLs(chromiumTipOfTree),
|
downloadURLs: this._downloadURLs(chromiumTipOfTree),
|
||||||
browserVersion: chromiumTipOfTree.browserVersion,
|
browserVersion: chromiumTipOfTree.browserVersion,
|
||||||
_install: () => this._downloadExecutable(chromiumTipOfTree, chromiumTipOfTreeExecutable),
|
_install: () => this._downloadExecutable(chromiumTipOfTree, chromiumTipOfTreeExecutable),
|
||||||
|
|
@ -573,7 +631,7 @@ export class Registry {
|
||||||
executablePath: () => chromiumExecutable,
|
executablePath: () => chromiumExecutable,
|
||||||
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium', chromiumExecutable, chromium.installByDefault, sdkLanguage),
|
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium', chromiumExecutable, chromium.installByDefault, sdkLanguage),
|
||||||
installType: 'download-on-demand',
|
installType: 'download-on-demand',
|
||||||
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'chromium', chromium.dir, ['chrome-linux'], [], ['chrome-win']),
|
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, chromium.dir, ['chrome-linux'], [], ['chrome-win']),
|
||||||
downloadURLs: this._downloadURLs(chromium),
|
downloadURLs: this._downloadURLs(chromium),
|
||||||
browserVersion: chromium.browserVersion,
|
browserVersion: chromium.browserVersion,
|
||||||
_install: () => this._downloadExecutable(chromium, chromiumExecutable),
|
_install: () => this._downloadExecutable(chromium, chromiumExecutable),
|
||||||
|
|
@ -591,7 +649,7 @@ export class Registry {
|
||||||
executablePath: () => firefoxExecutable,
|
executablePath: () => firefoxExecutable,
|
||||||
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('firefox', firefoxExecutable, firefox.installByDefault, sdkLanguage),
|
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('firefox', firefoxExecutable, firefox.installByDefault, sdkLanguage),
|
||||||
installType: firefox.installByDefault ? 'download-by-default' : 'download-on-demand',
|
installType: firefox.installByDefault ? 'download-by-default' : 'download-on-demand',
|
||||||
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'firefox', firefox.dir, ['firefox'], [], ['firefox']),
|
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, firefox.dir, ['firefox'], [], ['firefox']),
|
||||||
downloadURLs: this._downloadURLs(firefox),
|
downloadURLs: this._downloadURLs(firefox),
|
||||||
browserVersion: firefox.browserVersion,
|
browserVersion: firefox.browserVersion,
|
||||||
_install: () => this._downloadExecutable(firefox, firefoxExecutable),
|
_install: () => this._downloadExecutable(firefox, firefoxExecutable),
|
||||||
|
|
@ -609,7 +667,7 @@ export class Registry {
|
||||||
executablePath: () => firefoxBetaExecutable,
|
executablePath: () => firefoxBetaExecutable,
|
||||||
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('firefox-beta', firefoxBetaExecutable, firefoxBeta.installByDefault, sdkLanguage),
|
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('firefox-beta', firefoxBetaExecutable, firefoxBeta.installByDefault, sdkLanguage),
|
||||||
installType: firefoxBeta.installByDefault ? 'download-by-default' : 'download-on-demand',
|
installType: firefoxBeta.installByDefault ? 'download-by-default' : 'download-on-demand',
|
||||||
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'firefox', firefoxBeta.dir, ['firefox'], [], ['firefox']),
|
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, firefoxBeta.dir, ['firefox'], [], ['firefox']),
|
||||||
downloadURLs: this._downloadURLs(firefoxBeta),
|
downloadURLs: this._downloadURLs(firefoxBeta),
|
||||||
browserVersion: firefoxBeta.browserVersion,
|
browserVersion: firefoxBeta.browserVersion,
|
||||||
_install: () => this._downloadExecutable(firefoxBeta, firefoxBetaExecutable),
|
_install: () => this._downloadExecutable(firefoxBeta, firefoxBetaExecutable),
|
||||||
|
|
@ -637,7 +695,7 @@ export class Registry {
|
||||||
executablePath: () => webkitExecutable,
|
executablePath: () => webkitExecutable,
|
||||||
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('webkit', webkitExecutable, webkit.installByDefault, sdkLanguage),
|
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('webkit', webkitExecutable, webkit.installByDefault, sdkLanguage),
|
||||||
installType: webkit.installByDefault ? 'download-by-default' : 'download-on-demand',
|
installType: webkit.installByDefault ? 'download-by-default' : 'download-on-demand',
|
||||||
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'webkit', webkit.dir, webkitLinuxLddDirectories, ['libGLESv2.so.2', 'libx264.so'], ['']),
|
_validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, webkit.dir, webkitLinuxLddDirectories, ['libGLESv2.so.2', 'libx264.so'], ['']),
|
||||||
downloadURLs: this._downloadURLs(webkit),
|
downloadURLs: this._downloadURLs(webkit),
|
||||||
browserVersion: webkit.browserVersion,
|
browserVersion: webkit.browserVersion,
|
||||||
_install: () => this._downloadExecutable(webkit, webkitExecutable),
|
_install: () => this._downloadExecutable(webkit, webkitExecutable),
|
||||||
|
|
@ -835,7 +893,7 @@ export class Registry {
|
||||||
return Array.from(set);
|
return Array.from(set);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _validateHostRequirements(sdkLanguage: string, browserName: BrowserName, browserDirectory: string, linuxLddDirectories: string[], dlOpenLibraries: string[], windowsExeAndDllDirectories: string[]) {
|
private async _validateHostRequirements(sdkLanguage: string, browserDirectory: string, linuxLddDirectories: string[], dlOpenLibraries: string[], windowsExeAndDllDirectories: string[]) {
|
||||||
if (os.platform() === 'linux')
|
if (os.platform() === 'linux')
|
||||||
return await validateDependenciesLinux(sdkLanguage, linuxLddDirectories.map(d => path.join(browserDirectory, d)), dlOpenLibraries);
|
return await validateDependenciesLinux(sdkLanguage, linuxLddDirectories.map(d => path.join(browserDirectory, d)), dlOpenLibraries);
|
||||||
if (os.platform() === 'win32' && os.arch() === 'x64')
|
if (os.platform() === 'win32' && os.arch() === 'x64')
|
||||||
|
|
|
||||||
|
|
@ -359,8 +359,10 @@ export const deps: any = {
|
||||||
'libx264-163',
|
'libx264-163',
|
||||||
'libatomic1',
|
'libatomic1',
|
||||||
'libevent-2.1-7',
|
'libevent-2.1-7',
|
||||||
|
'libavif13',
|
||||||
],
|
],
|
||||||
lib2package: {
|
lib2package: {
|
||||||
|
'libavif.so.13': 'libavif13',
|
||||||
'libsoup-3.0.so.0': 'libsoup-3.0-0',
|
'libsoup-3.0.so.0': 'libsoup-3.0-0',
|
||||||
'libasound.so.2': 'libasound2',
|
'libasound.so.2': 'libasound2',
|
||||||
'libatk-1.0.so.0': 'libatk1.0-0',
|
'libatk-1.0.so.0': 'libatk1.0-0',
|
||||||
|
|
@ -566,9 +568,11 @@ export const deps: any = {
|
||||||
'libxkbcommon0',
|
'libxkbcommon0',
|
||||||
'libxml2',
|
'libxml2',
|
||||||
'libxslt1.1',
|
'libxslt1.1',
|
||||||
'libx264-164'
|
'libx264-164',
|
||||||
|
'libavif16',
|
||||||
],
|
],
|
||||||
lib2package: {
|
lib2package: {
|
||||||
|
'libavif.so.16': 'libavif16',
|
||||||
'libasound.so.2': 'libasound2t64',
|
'libasound.so.2': 'libasound2t64',
|
||||||
'libatk-1.0.so.0': 'libatk1.0-0t64',
|
'libatk-1.0.so.0': 'libatk1.0-0t64',
|
||||||
'libatk-bridge-2.0.so.0': 'libatk-bridge2.0-0t64',
|
'libatk-bridge-2.0.so.0': 'libatk-bridge2.0-0t64',
|
||||||
|
|
@ -994,8 +998,10 @@ export const deps: any = {
|
||||||
'libxslt1.1',
|
'libxslt1.1',
|
||||||
'libatomic1',
|
'libatomic1',
|
||||||
'libevent-2.1-7',
|
'libevent-2.1-7',
|
||||||
|
'libavif15',
|
||||||
],
|
],
|
||||||
lib2package: {
|
lib2package: {
|
||||||
|
'libavif.so.15': 'libavif15',
|
||||||
'libsoup-3.0.so.0': 'libsoup-3.0-0',
|
'libsoup-3.0.so.0': 'libsoup-3.0-0',
|
||||||
'libasound.so.2': 'libasound2',
|
'libasound.so.2': 'libasound2',
|
||||||
'libatk-1.0.so.0': 'libatk1.0-0',
|
'libatk-1.0.so.0': 'libatk1.0-0',
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,6 @@ import { resolveReporterOutputPath } from '../util';
|
||||||
export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' };
|
export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' };
|
||||||
export const kOutputSymbol = Symbol('output');
|
export const kOutputSymbol = Symbol('output');
|
||||||
|
|
||||||
type Annotation = {
|
|
||||||
title: string;
|
|
||||||
message: string;
|
|
||||||
location?: Location;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ErrorDetails = {
|
type ErrorDetails = {
|
||||||
message: string;
|
message: string;
|
||||||
location?: Location;
|
location?: Location;
|
||||||
|
|
@ -260,9 +254,7 @@ export class BaseReporter implements ReporterV2 {
|
||||||
private _printFailures(failures: TestCase[]) {
|
private _printFailures(failures: TestCase[]) {
|
||||||
console.log('');
|
console.log('');
|
||||||
failures.forEach((test, index) => {
|
failures.forEach((test, index) => {
|
||||||
console.log(formatFailure(this.config, test, {
|
console.log(formatFailure(this.config, test, index + 1));
|
||||||
index: index + 1,
|
|
||||||
}).message);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,14 +277,8 @@ export class BaseReporter implements ReporterV2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatFailure(config: FullConfig, test: TestCase, options: {index?: number, includeStdio?: boolean, includeAttachments?: boolean} = {}): {
|
export function formatFailure(config: FullConfig, test: TestCase, index?: number): string {
|
||||||
message: string,
|
|
||||||
annotations: Annotation[]
|
|
||||||
} {
|
|
||||||
const { index, includeStdio, includeAttachments = true } = options;
|
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
const title = formatTestTitle(config, test);
|
|
||||||
const annotations: Annotation[] = [];
|
|
||||||
const header = formatTestHeader(config, test, { indent: ' ', index, mode: 'error' });
|
const header = formatTestHeader(config, test, { indent: ' ', index, mode: 'error' });
|
||||||
lines.push(colors.red(header));
|
lines.push(colors.red(header));
|
||||||
for (const result of test.results) {
|
for (const result of test.results) {
|
||||||
|
|
@ -307,62 +293,48 @@ export function formatFailure(config: FullConfig, test: TestCase, options: {inde
|
||||||
}
|
}
|
||||||
resultLines.push(...retryLines);
|
resultLines.push(...retryLines);
|
||||||
resultLines.push(...errors.map(error => '\n' + error.message));
|
resultLines.push(...errors.map(error => '\n' + error.message));
|
||||||
if (includeAttachments) {
|
for (let i = 0; i < result.attachments.length; ++i) {
|
||||||
for (let i = 0; i < result.attachments.length; ++i) {
|
const attachment = result.attachments[i];
|
||||||
const attachment = result.attachments[i];
|
const hasPrintableContent = attachment.contentType.startsWith('text/');
|
||||||
const hasPrintableContent = attachment.contentType.startsWith('text/');
|
if (!attachment.path && !hasPrintableContent)
|
||||||
if (!attachment.path && !hasPrintableContent)
|
continue;
|
||||||
continue;
|
|
||||||
resultLines.push('');
|
|
||||||
resultLines.push(colors.cyan(separator(` attachment #${i + 1}: ${attachment.name} (${attachment.contentType})`)));
|
|
||||||
if (attachment.path) {
|
|
||||||
const relativePath = path.relative(process.cwd(), attachment.path);
|
|
||||||
resultLines.push(colors.cyan(` ${relativePath}`));
|
|
||||||
// Make this extensible
|
|
||||||
if (attachment.name === 'trace') {
|
|
||||||
const packageManagerCommand = getPackageManagerExecCommand();
|
|
||||||
resultLines.push(colors.cyan(` Usage:`));
|
|
||||||
resultLines.push('');
|
|
||||||
resultLines.push(colors.cyan(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`));
|
|
||||||
resultLines.push('');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (attachment.contentType.startsWith('text/') && attachment.body) {
|
|
||||||
let text = attachment.body.toString();
|
|
||||||
if (text.length > 300)
|
|
||||||
text = text.slice(0, 300) + '...';
|
|
||||||
for (const line of text.split('\n'))
|
|
||||||
resultLines.push(colors.cyan(` ${line}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resultLines.push(colors.cyan(separator(' ')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const output = ((result as any)[kOutputSymbol] || []) as TestResultOutput[];
|
|
||||||
if (includeStdio && output.length) {
|
|
||||||
const outputText = output.map(({ chunk, type }) => {
|
|
||||||
const text = chunk.toString('utf8');
|
|
||||||
if (type === 'stderr')
|
|
||||||
return colors.red(stripAnsiEscapes(text));
|
|
||||||
return text;
|
|
||||||
}).join('');
|
|
||||||
resultLines.push('');
|
resultLines.push('');
|
||||||
resultLines.push(colors.gray(separator('--- Test output')) + '\n\n' + outputText + '\n' + separator());
|
resultLines.push(colors.cyan(separator(` attachment #${i + 1}: ${attachment.name} (${attachment.contentType})`)));
|
||||||
}
|
if (attachment.path) {
|
||||||
for (const error of errors) {
|
const relativePath = path.relative(process.cwd(), attachment.path);
|
||||||
annotations.push({
|
resultLines.push(colors.cyan(` ${relativePath}`));
|
||||||
location: error.location,
|
// Make this extensible
|
||||||
title,
|
if (attachment.name === 'trace') {
|
||||||
message: [header, ...retryLines, error.message].join('\n'),
|
const packageManagerCommand = getPackageManagerExecCommand();
|
||||||
});
|
resultLines.push(colors.cyan(` Usage:`));
|
||||||
|
resultLines.push('');
|
||||||
|
resultLines.push(colors.cyan(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`));
|
||||||
|
resultLines.push('');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (attachment.contentType.startsWith('text/') && attachment.body) {
|
||||||
|
let text = attachment.body.toString();
|
||||||
|
if (text.length > 300)
|
||||||
|
text = text.slice(0, 300) + '...';
|
||||||
|
for (const line of text.split('\n'))
|
||||||
|
resultLines.push(colors.cyan(` ${line}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultLines.push(colors.cyan(separator(' ')));
|
||||||
}
|
}
|
||||||
lines.push(...resultLines);
|
lines.push(...resultLines);
|
||||||
}
|
}
|
||||||
lines.push('');
|
lines.push('');
|
||||||
return {
|
return lines.join('\n');
|
||||||
message: lines.join('\n'),
|
}
|
||||||
annotations
|
|
||||||
};
|
export function formatRetry(result: TestResult) {
|
||||||
|
const retryLines = [];
|
||||||
|
if (result.retry) {
|
||||||
|
retryLines.push('');
|
||||||
|
retryLines.push(colors.gray(separator(` Retry #${result.retry}`)));
|
||||||
|
}
|
||||||
|
return retryLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
function quotePathIfNeeded(path: string): string {
|
function quotePathIfNeeded(path: string): string {
|
||||||
|
|
@ -428,7 +400,7 @@ export function formatTestTitle(config: FullConfig, test: TestCase, step?: TestS
|
||||||
return `${projectTitle}${location} › ${titles.join(' › ')}${stepSuffix(step)}${tags}`;
|
return `${projectTitle}${location} › ${titles.join(' › ')}${stepSuffix(step)}${tags}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTestHeader(config: FullConfig, test: TestCase, options: { indent?: string, index?: number, mode?: 'default' | 'error' } = {}): string {
|
export function formatTestHeader(config: FullConfig, test: TestCase, options: { indent?: string, index?: number, mode?: 'default' | 'error' } = {}): string {
|
||||||
const title = formatTestTitle(config, test);
|
const title = formatTestTitle(config, test);
|
||||||
const header = `${options.indent || ''}${options.index ? options.index + ') ' : ''}${title}`;
|
const header = `${options.indent || ''}${options.index ? options.index + ') ' : ''}${title}`;
|
||||||
let fullHeader = header;
|
let fullHeader = header;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import { ms as milliseconds } from 'playwright-core/lib/utilsBundle';
|
import { ms as milliseconds } from 'playwright-core/lib/utilsBundle';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { BaseReporter, formatError, formatFailure, stripAnsiEscapes } from './base';
|
import { BaseReporter, colors, formatError, formatResultFailure, formatRetry, formatTestHeader, formatTestTitle, stripAnsiEscapes } from './base';
|
||||||
import type { TestCase, FullResult, TestError } from '../../types/testReporter';
|
import type { TestCase, FullResult, TestError } from '../../types/testReporter';
|
||||||
|
|
||||||
type GitHubLogType = 'debug' | 'notice' | 'warning' | 'error';
|
type GitHubLogType = 'debug' | 'notice' | 'warning' | 'error';
|
||||||
|
|
@ -100,22 +100,23 @@ export class GitHubReporter extends BaseReporter {
|
||||||
|
|
||||||
private _printFailureAnnotations(failures: TestCase[]) {
|
private _printFailureAnnotations(failures: TestCase[]) {
|
||||||
failures.forEach((test, index) => {
|
failures.forEach((test, index) => {
|
||||||
const { annotations } = formatFailure(this.config, test, {
|
const title = formatTestTitle(this.config, test);
|
||||||
index: index + 1,
|
const header = formatTestHeader(this.config, test, { indent: ' ', index: index + 1, mode: 'error' });
|
||||||
includeStdio: true,
|
for (const result of test.results) {
|
||||||
includeAttachments: false,
|
const errors = formatResultFailure(test, result, ' ', colors.enabled);
|
||||||
});
|
for (const error of errors) {
|
||||||
annotations.forEach(({ location, title, message }) => {
|
const options: GitHubLogOptions = {
|
||||||
const options: GitHubLogOptions = {
|
file: workspaceRelativePath(error.location?.file || test.location.file),
|
||||||
file: workspaceRelativePath(location?.file || test.location.file),
|
title,
|
||||||
title,
|
};
|
||||||
};
|
if (error.location) {
|
||||||
if (location) {
|
options.line = error.location.line;
|
||||||
options.line = location.line;
|
options.col = error.location.column;
|
||||||
options.col = location.column;
|
}
|
||||||
|
const message = [header, ...formatRetry(result), error.message].join('\n');
|
||||||
|
this.githubLogger.error(message, options);
|
||||||
}
|
}
|
||||||
this.githubLogger.error(message, options);
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ class JUnitReporter implements ReporterV2 {
|
||||||
message: `${path.basename(test.location.file)}:${test.location.line}:${test.location.column} ${test.title}`,
|
message: `${path.basename(test.location.file)}:${test.location.line}:${test.location.column} ${test.title}`,
|
||||||
type: 'FAILURE',
|
type: 'FAILURE',
|
||||||
},
|
},
|
||||||
text: stripAnsiEscapes(formatFailure(this.config, test).message)
|
text: stripAnsiEscapes(formatFailure(this.config, test))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,9 +82,7 @@ class LineReporter extends BaseReporter {
|
||||||
if (!this.willRetry(test) && (test.outcome() === 'flaky' || test.outcome() === 'unexpected' || result.status === 'interrupted')) {
|
if (!this.willRetry(test) && (test.outcome() === 'flaky' || test.outcome() === 'unexpected' || result.status === 'interrupted')) {
|
||||||
if (!process.env.PW_TEST_DEBUG_REPORTERS)
|
if (!process.env.PW_TEST_DEBUG_REPORTERS)
|
||||||
process.stdout.write(`\u001B[1A\u001B[2K`);
|
process.stdout.write(`\u001B[1A\u001B[2K`);
|
||||||
console.log(formatFailure(this.config, test, {
|
console.log(formatFailure(this.config, test, ++this._failures));
|
||||||
index: ++this._failures
|
|
||||||
}).message);
|
|
||||||
console.log();
|
console.log();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CallLog, Mode, Source } from './recorderTypes';
|
import type { CallLog, ElementInfo, Mode, Source } from './recorderTypes';
|
||||||
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
|
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
|
||||||
import { SplitView } from '@web/components/splitView';
|
import { SplitView } from '@web/components/splitView';
|
||||||
import { TabbedPane } from '@web/components/tabbedPane';
|
import { TabbedPane } from '@web/components/tabbedPane';
|
||||||
|
|
@ -44,6 +44,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
const [selectedFileId, setSelectedFileId] = React.useState<string | undefined>();
|
const [selectedFileId, setSelectedFileId] = React.useState<string | undefined>();
|
||||||
const [runningFileId, setRunningFileId] = React.useState<string | undefined>();
|
const [runningFileId, setRunningFileId] = React.useState<string | undefined>();
|
||||||
const [selectedTab, setSelectedTab] = React.useState<string>('log');
|
const [selectedTab, setSelectedTab] = React.useState<string>('log');
|
||||||
|
const [ariaSnapshot, setAriaSnapshot] = React.useState<string | undefined>();
|
||||||
|
|
||||||
const fileId = selectedFileId || runningFileId || sources[0]?.id;
|
const fileId = selectedFileId || runningFileId || sources[0]?.id;
|
||||||
|
|
||||||
|
|
@ -57,11 +58,18 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
}, [sources, fileId]);
|
}, [sources, fileId]);
|
||||||
|
|
||||||
const [locator, setLocator] = React.useState('');
|
const [locator, setLocator] = React.useState('');
|
||||||
window.playwrightSetSelector = (selector: string, focus?: boolean) => {
|
window.playwrightElementPicked = (elementInfo: ElementInfo, userGesture?: boolean) => {
|
||||||
const language = source.language;
|
const language = source.language;
|
||||||
if (focus)
|
setLocator(asLocator(language, elementInfo.selector));
|
||||||
|
setAriaSnapshot(elementInfo.ariaSnapshot);
|
||||||
|
if (userGesture && selectedTab !== 'locator' && selectedTab !== 'aria')
|
||||||
setSelectedTab('locator');
|
setSelectedTab('locator');
|
||||||
setLocator(asLocator(language, selector));
|
|
||||||
|
if (mode === 'inspecting' && selectedTab === 'aria') {
|
||||||
|
// Keep exploring aria.
|
||||||
|
} else {
|
||||||
|
window.dispatch({ event: 'setMode', params: { mode: mode === 'inspecting' ? 'standby' : 'recording' } }).catch(() => { });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.playwrightSetRunningFile = setRunningFileId;
|
window.playwrightSetRunningFile = setRunningFileId;
|
||||||
|
|
@ -94,7 +102,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
}, [paused]);
|
}, [paused]);
|
||||||
|
|
||||||
const onEditorChange = React.useCallback((selector: string) => {
|
const onEditorChange = React.useCallback((selector: string) => {
|
||||||
if (mode === 'none')
|
if (mode === 'none' || mode === 'inspecting')
|
||||||
window.dispatch({ event: 'setMode', params: { mode: 'standby' } });
|
window.dispatch({ event: 'setMode', params: { mode: 'standby' } });
|
||||||
setLocator(selector);
|
setLocator(selector);
|
||||||
window.dispatch({ event: 'selectorUpdated', params: { selector } });
|
window.dispatch({ event: 'selectorUpdated', params: { selector } });
|
||||||
|
|
@ -157,7 +165,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
sidebarSize={200}
|
sidebarSize={200}
|
||||||
main={<CodeMirrorWrapper text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine} readOnly={true} lineNumbers={true} />}
|
main={<CodeMirrorWrapper text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine} readOnly={true} lineNumbers={true} />}
|
||||||
sidebar={<TabbedPane
|
sidebar={<TabbedPane
|
||||||
rightToolbar={selectedTab === 'locator' ? [<ToolbarButton key={1} icon='files' title='Copy' onClick={() => copy(locator)} />] : []}
|
rightToolbar={selectedTab === 'locator' || selectedTab === 'aria' ? [<ToolbarButton key={1} icon='files' title='Copy' onClick={() => copy((selectedTab === 'locator' ? locator : ariaSnapshot) || '')} />] : []}
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
id: 'locator',
|
id: 'locator',
|
||||||
|
|
@ -169,6 +177,11 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
title: 'Log',
|
title: 'Log',
|
||||||
render: () => <CallLogView language={source.language} log={Array.from(log.values())} />
|
render: () => <CallLogView language={source.language} log={Array.from(log.values())} />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'aria',
|
||||||
|
title: 'Accessibility',
|
||||||
|
render: () => <CodeMirrorWrapper text={ariaSnapshot || ''} language={'python'} readOnly={true} wrapLines={true} />
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
selectedTab={selectedTab}
|
selectedTab={selectedTab}
|
||||||
setSelectedTab={setSelectedTab}
|
setSelectedTab={setSelectedTab}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,11 @@ export type Mode =
|
||||||
| 'assertingValue'
|
| 'assertingValue'
|
||||||
| 'assertingSnapshot';
|
| 'assertingSnapshot';
|
||||||
|
|
||||||
|
export type ElementInfo = {
|
||||||
|
selector: string;
|
||||||
|
ariaSnapshot: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type EventData = {
|
export type EventData = {
|
||||||
event:
|
event:
|
||||||
| 'clear'
|
| 'clear'
|
||||||
|
|
@ -98,7 +103,7 @@ declare global {
|
||||||
playwrightSetOverlayVisible: (visible: boolean) => void;
|
playwrightSetOverlayVisible: (visible: boolean) => void;
|
||||||
playwrightUpdateLogs: (callLogs: CallLog[]) => void;
|
playwrightUpdateLogs: (callLogs: CallLog[]) => void;
|
||||||
playwrightSetRunningFile: (file: string | undefined) => void;
|
playwrightSetRunningFile: (file: string | undefined) => void;
|
||||||
playwrightSetSelector: (selector: string, focus?: boolean) => void;
|
playwrightElementPicked: (elementInfo: ElementInfo, userGesture?: boolean) => void;
|
||||||
playwrightSourcesEchoForTest: Source[];
|
playwrightSourcesEchoForTest: Source[];
|
||||||
dispatch(data: any): Promise<void>;
|
dispatch(data: any): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
"paths": {
|
"paths": {
|
||||||
"@isomorphic/*": ["../playwright-core/src/utils/isomorphic/*"],
|
"@isomorphic/*": ["../playwright-core/src/utils/isomorphic/*"],
|
||||||
"@protocol/*": ["../protocol/src/*"],
|
"@protocol/*": ["../protocol/src/*"],
|
||||||
|
"@recorder/*": ["../recorder/src/*"],
|
||||||
"@web/*": ["../web/src/*"],
|
"@web/*": ["../web/src/*"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import type { Language } from '@isomorphic/locatorGenerators';
|
||||||
import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser';
|
import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser';
|
||||||
import { TabbedPaneTab } from '@web/components/tabbedPane';
|
import { TabbedPaneTab } from '@web/components/tabbedPane';
|
||||||
import { BrowserFrame } from './browserFrame';
|
import { BrowserFrame } from './browserFrame';
|
||||||
|
import type { ElementInfo } from '@recorder/recorderTypes';
|
||||||
|
|
||||||
export const SnapshotTabsView: React.FunctionComponent<{
|
export const SnapshotTabsView: React.FunctionComponent<{
|
||||||
action: ActionTraceEvent | undefined,
|
action: ActionTraceEvent | undefined,
|
||||||
|
|
@ -244,8 +245,8 @@ export const InspectModeController: React.FunctionComponent<{
|
||||||
testIdAttributeName,
|
testIdAttributeName,
|
||||||
overlay: { offsetX: 0 },
|
overlay: { offsetX: 0 },
|
||||||
}, {
|
}, {
|
||||||
async setSelector(selector: string) {
|
async elementPicked(elementInfo: ElementInfo) {
|
||||||
setHighlightedLocator(asLocator(sdkLanguage, frameSelector + selector));
|
setHighlightedLocator(asLocator(sdkLanguage, frameSelector + elementInfo.selector));
|
||||||
},
|
},
|
||||||
highlightUpdated() {
|
highlightUpdated() {
|
||||||
for (const r of recorders) {
|
for (const r of recorders) {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import 'codemirror-shadow-1/mode/python/python';
|
||||||
import 'codemirror-shadow-1/mode/clike/clike';
|
import 'codemirror-shadow-1/mode/clike/clike';
|
||||||
import 'codemirror-shadow-1/mode/markdown/markdown';
|
import 'codemirror-shadow-1/mode/markdown/markdown';
|
||||||
import 'codemirror-shadow-1/addon/mode/simple';
|
import 'codemirror-shadow-1/addon/mode/simple';
|
||||||
|
import 'codemirror-shadow-1/mode/yaml/yaml';
|
||||||
|
|
||||||
export type CodeMirror = typeof codemirrorType;
|
export type CodeMirror = typeof codemirrorType;
|
||||||
export default codemirror;
|
export default codemirror;
|
||||||
|
|
|
||||||
|
|
@ -400,7 +400,7 @@ it('should be able to render avif images', {
|
||||||
}
|
}
|
||||||
}, async ({ page, server, browserName, platform }) => {
|
}, async ({ page, server, browserName, platform }) => {
|
||||||
it.fixme(browserName === 'webkit' && platform === 'win32');
|
it.fixme(browserName === 'webkit' && platform === 'win32');
|
||||||
it.fixme(browserName === 'webkit' && platform === 'linux', 'https://github.com/microsoft/playwright/issues/32673');
|
it.skip(browserName === 'webkit' && hostPlatform.startsWith('ubuntu20.04'), 'Ubuntu 20.04 is frozen');
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`<img src="${server.PREFIX}/rgb.avif" onerror="window.error = true">`);
|
await page.setContent(`<img src="${server.PREFIX}/rgb.avif" onerror="window.error = true">`);
|
||||||
await expect.poll(() => page.locator('img').boundingBox()).toEqual(expect.objectContaining({
|
await expect.poll(() => page.locator('img').boundingBox()).toEqual(expect.objectContaining({
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,8 @@ it('should snapshot list with accessible name', async ({ page }) => {
|
||||||
`);
|
`);
|
||||||
await checkAndMatchSnapshot(page.locator('body'), `
|
await checkAndMatchSnapshot(page.locator('body'), `
|
||||||
- list "my list":
|
- list "my list":
|
||||||
- listitem: one
|
- listitem: "one"
|
||||||
- listitem: two
|
- listitem: "two"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -92,7 +92,7 @@ it('should allow text nodes', async ({ page }) => {
|
||||||
|
|
||||||
await checkAndMatchSnapshot(page.locator('body'), `
|
await checkAndMatchSnapshot(page.locator('body'), `
|
||||||
- heading "Microsoft" [level=1]
|
- heading "Microsoft" [level=1]
|
||||||
- text: Open source projects and samples from Microsoft
|
- text: "Open source projects and samples from Microsoft"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -105,7 +105,7 @@ it('should snapshot details visibility', async ({ page }) => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await checkAndMatchSnapshot(page.locator('body'), `
|
await checkAndMatchSnapshot(page.locator('body'), `
|
||||||
- group: Summary
|
- group: "Summary"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -145,10 +145,10 @@ it('should snapshot integration', async ({ page }) => {
|
||||||
|
|
||||||
await checkAndMatchSnapshot(page.locator('body'), `
|
await checkAndMatchSnapshot(page.locator('body'), `
|
||||||
- heading "Microsoft" [level=1]
|
- heading "Microsoft" [level=1]
|
||||||
- text: Open source projects and samples from Microsoft
|
- text: "Open source projects and samples from Microsoft"
|
||||||
- list:
|
- list:
|
||||||
- listitem:
|
- listitem:
|
||||||
- group: Verified
|
- group: "Verified"
|
||||||
- listitem:
|
- listitem:
|
||||||
- link "Sponsor"
|
- link "Sponsor"
|
||||||
`);
|
`);
|
||||||
|
|
@ -164,7 +164,7 @@ it('should support multiline text', async ({ page }) => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await checkAndMatchSnapshot(page.locator('body'), `
|
await checkAndMatchSnapshot(page.locator('body'), `
|
||||||
- paragraph: Line 1 Line 2 Line 3
|
- paragraph: "Line 1 Line 2 Line 3"
|
||||||
`);
|
`);
|
||||||
await expect(page.locator('body')).toMatchAriaSnapshot(`
|
await expect(page.locator('body')).toMatchAriaSnapshot(`
|
||||||
- paragraph: |
|
- paragraph: |
|
||||||
|
|
@ -180,7 +180,7 @@ it('should concatenate span text', async ({ page }) => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await checkAndMatchSnapshot(page.locator('body'), `
|
await checkAndMatchSnapshot(page.locator('body'), `
|
||||||
- text: One Two Three
|
- text: "One Two Three"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -190,7 +190,7 @@ it('should concatenate span text 2', async ({ page }) => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await checkAndMatchSnapshot(page.locator('body'), `
|
await checkAndMatchSnapshot(page.locator('body'), `
|
||||||
- text: One Two Three
|
- text: "One Two Three"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -200,7 +200,7 @@ it('should concatenate div text with spaces', async ({ page }) => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await checkAndMatchSnapshot(page.locator('body'), `
|
await checkAndMatchSnapshot(page.locator('body'), `
|
||||||
- text: One Two Three
|
- text: "One Two Three"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -362,12 +362,12 @@ it('should snapshot inner text', async ({ page }) => {
|
||||||
|
|
||||||
await checkAndMatchSnapshot(page.locator('body'), `
|
await checkAndMatchSnapshot(page.locator('body'), `
|
||||||
- listitem:
|
- listitem:
|
||||||
- text: a.test.ts
|
- text: "a.test.ts"
|
||||||
- button "Run"
|
- button "Run"
|
||||||
- button "Show source"
|
- button "Show source"
|
||||||
- button "Watch"
|
- button "Watch"
|
||||||
- listitem:
|
- listitem:
|
||||||
- text: snapshot 30ms
|
- text: "snapshot 30ms"
|
||||||
- button "Run"
|
- button "Run"
|
||||||
- button "Show source"
|
- button "Show source"
|
||||||
- button "Watch"
|
- button "Watch"
|
||||||
|
|
@ -382,6 +382,6 @@ it('should include pseudo codepoints', async ({ page, server }) => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await checkAndMatchSnapshot(page.locator('body'), `
|
await checkAndMatchSnapshot(page.locator('body'), `
|
||||||
- paragraph: \ueab2hello
|
- paragraph: "\ueab2hello"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@ for (const webPackage of ['html-reporter', 'recorder', 'trace-viewer']) {
|
||||||
'vite',
|
'vite',
|
||||||
'build',
|
'build',
|
||||||
...(watchMode ? ['--watch', '--minify=false'] : []),
|
...(watchMode ? ['--watch', '--minify=false'] : []),
|
||||||
...(withSourceMaps ? ['--sourcemap'] : []),
|
...(withSourceMaps ? ['--sourcemap=inline'] : []),
|
||||||
],
|
],
|
||||||
shell: true,
|
shell: true,
|
||||||
cwd: path.join(__dirname, '..', '..', 'packages', webPackage),
|
cwd: path.join(__dirname, '..', '..', 'packages', webPackage),
|
||||||
|
|
@ -299,7 +299,7 @@ steps.push({
|
||||||
'vite.sw.config.ts',
|
'vite.sw.config.ts',
|
||||||
'build',
|
'build',
|
||||||
...(watchMode ? ['--watch', '--minify=false'] : []),
|
...(watchMode ? ['--watch', '--minify=false'] : []),
|
||||||
...(withSourceMaps ? ['--sourcemap'] : []),
|
...(withSourceMaps ? ['--sourcemap=inline'] : []),
|
||||||
],
|
],
|
||||||
shell: true,
|
shell: true,
|
||||||
cwd: path.join(__dirname, '..', '..', 'packages', 'trace-viewer'),
|
cwd: path.join(__dirname, '..', '..', 'packages', 'trace-viewer'),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue