Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/src/api/class-screencast.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ If a screencast is already active (e.g. started by tracing or video recording),

Defaults to 800×800.

### option: Screencast.start.annotate
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it on screencast? there were requests to see the actions in the running browser

* since: v1.59
- `annotate` ?<[Object]>
- `delay` ?<[int]> How long each annotation is displayed in milliseconds. Defaults to `500`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's explain that all input actions are delayed by this amount - this came as a surprise while reading the code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll follow up, want to add a test step there.


If specified, enables visual annotations on interacted elements during screencast. Interacted elements are highlighted with a semi-transparent blue box and click points are shown as red circles.

## async method: Screencast.stop
* since: v1.59

Expand Down
7 changes: 7 additions & 0 deletions docs/src/api/class-video.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ Path where the video should be saved when the recording is stopped.

Optional dimensions of the recorded video. If not specified the size will be equal to page viewport scaled down to fit into 800x800. Actual picture of the page will be scaled down if necessary to fit the specified size.

### option: Video.start.annotate
* since: v1.59
- `annotate` ?<[Object]>
- `delay` ?<[int]> How long each annotation is displayed in milliseconds. Defaults to `500`.

If specified, enables visual annotations on interacted elements during video recording. Interacted elements are highlighted with a semi-transparent blue box and click points are shown as red circles.

## async method: Video.stop
* since: v1.59

Expand Down
2 changes: 2 additions & 0 deletions docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,8 @@ When set to `minimal`, only record information necessary for routing from HAR. T
Actual picture of each page will be scaled down if necessary to fit the specified size.
- `width` <[int]> Video frame width.
- `height` <[int]> Video frame height.
- `annotate` ?<[Object]> If specified, enables visual annotations on interacted elements during video recording.
- `delay` ?<[int]> How long each annotation is displayed in milliseconds. Defaults to `500`.

Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. Make
sure to await [`method: BrowserContext.close`] for videos to be saved.
Expand Down
5 changes: 5 additions & 0 deletions packages/injected/src/highlight.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ x-pw-action-point {
z-index: 2;
}

@keyframes pw-fade-out {
from { opacity: 1; }
to { opacity: 0; }
}

x-pw-separator {
height: 1px;
margin: 6px 9px;
Expand Down
16 changes: 14 additions & 2 deletions packages/injected/src/highlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import type { InjectedScript } from './injectedScript';
type RenderedHighlightEntry = {
targetElement: Element,
color: string,
borderColor?: string,
fadeDuration?: number,
highlightElement: HTMLElement,
tooltipElement?: HTMLElement,
box?: DOMRect,
Expand All @@ -38,6 +40,8 @@ type RenderedHighlightEntry = {
export type HighlightEntry = {
element: Element,
color: string,
borderColor?: string,
fadeDuration?: number,
tooltipText?: string,
};

Expand Down Expand Up @@ -123,10 +127,14 @@ export class Highlight {
this._glassPaneElement.remove();
}

showActionPoint(x: number, y: number) {
showActionPoint(x: number, y: number, fadeDuration?: number) {
this._actionPointElement.style.top = y + 'px';
this._actionPointElement.style.left = x + 'px';
this._actionPointElement.hidden = false;
if (fadeDuration)
this._actionPointElement.style.animation = `pw-fade-out ${fadeDuration}ms ease-out forwards`;
else
this._actionPointElement.style.animation = '';
}

hideActionPoint() {
Expand Down Expand Up @@ -170,7 +178,7 @@ export class Highlight {
lineElement.textContent = entry.tooltipText;
tooltipElement.appendChild(lineElement);
}
this._renderedEntries.push({ targetElement: entry.element, color: entry.color, tooltipElement, highlightElement });
this._renderedEntries.push({ targetElement: entry.element, color: entry.color, borderColor: entry.borderColor, fadeDuration: entry.fadeDuration, tooltipElement, highlightElement });
}

// 2. Trigger layout while positioning tooltips and computing bounding boxes.
Expand Down Expand Up @@ -198,6 +206,10 @@ export class Highlight {
entry.highlightElement.style.width = box.width + 'px';
entry.highlightElement.style.height = box.height + 'px';
entry.highlightElement.style.display = 'block';
if (entry.borderColor)
entry.highlightElement.style.border = '2px solid ' + entry.borderColor;
if (entry.fadeDuration)
entry.highlightElement.style.animation = `pw-fade-out ${entry.fadeDuration}ms ease-out forwards`;

if (this._isUnderTest)
console.error('Highlight box for test: ' + JSON.stringify({ x: box.x, y: box.y, width: box.width, height: box.height })); // eslint-disable-line no-console
Expand Down
30 changes: 26 additions & 4 deletions packages/injected/src/injectedScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1290,14 +1290,19 @@ export class InjectedScript {
}

maskSelectors(selectors: ParsedSelector[], color: string) {
const highlight = this._createHighlight();
const elements = [];
for (const selector of selectors)
elements.push(this.querySelectorAll(selector, this.document.documentElement));
highlight.maskElements(elements.flat(), color);
}

private _createHighlight() {
if (this._highlight)
this.hideHighlight();
this._highlight = new Highlight(this);
this._highlight.install();
const elements = [];
for (const selector of selectors)
elements.push(this.querySelectorAll(selector, this.document.documentElement));
this._highlight.maskElements(elements.flat(), color);
return this._highlight;
}

highlight(selector: ParsedSelector) {
Expand All @@ -1308,6 +1313,23 @@ export class InjectedScript {
this._highlight.runHighlightOnRaf(selector);
}

highlightNode(node: Node, point?: { x: number, y: number }, delay?: number) {
const highlight = this._createHighlight();
const fadeDuration = delay ?? 500;

if (node.nodeType === Node.ELEMENT_NODE) {
const element = node as Element;
highlight.updateHighlight([{
element,
color: 'rgba(0, 128, 255, 0.15)',
borderColor: 'rgba(0, 128, 255, 0.6)',
fadeDuration,
}]);
}
if (point)
highlight.showActionPoint(point.x, point.y, fadeDuration);
}

hideHighlight() {
if (this._highlight) {
this._highlight.uninstall();
Expand Down
61 changes: 61 additions & 0 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10212,6 +10212,16 @@ export interface Browser {
*/
height: number;
};

/**
* If specified, enables visual annotations on interacted elements during video recording.
*/
annotate?: {
/**
* How long each annotation is displayed in milliseconds. Defaults to `500`.
*/
delay?: number;
};
};

/**
Expand Down Expand Up @@ -15485,6 +15495,16 @@ export interface BrowserType<Unused = {}> {
*/
height: number;
};

/**
* If specified, enables visual annotations on interacted elements during video recording.
*/
annotate?: {
/**
* How long each annotation is displayed in milliseconds. Defaults to `500`.
*/
delay?: number;
};
};

/**
Expand Down Expand Up @@ -17348,6 +17368,16 @@ export interface AndroidDevice {
*/
height: number;
};

/**
* If specified, enables visual annotations on interacted elements during video recording.
*/
annotate?: {
/**
* How long each annotation is displayed in milliseconds. Defaults to `500`.
*/
delay?: number;
};
};

/**
Expand Down Expand Up @@ -19928,6 +19958,16 @@ export interface Electron {
*/
height: number;
};

/**
* If specified, enables visual annotations on interacted elements during video recording.
*/
annotate?: {
/**
* How long each annotation is displayed in milliseconds. Defaults to `500`.
*/
delay?: number;
};
};

/**
Expand Down Expand Up @@ -22027,6 +22067,17 @@ export interface Video {
* @param options
*/
start(options?: {
/**
* If specified, enables visual annotations on interacted elements during video recording. Interacted elements are
* highlighted with a semi-transparent blue box and click points are shown as red circles.
*/
annotate?: {
/**
* How long each annotation is displayed in milliseconds. Defaults to `500`.
*/
delay?: number;
};

/**
* Path where the video should be saved when the recording is stopped.
*/
Expand Down Expand Up @@ -22932,6 +22983,16 @@ export interface BrowserContextOptions {
*/
height: number;
};

/**
* If specified, enables visual annotations on interacted elements during video recording.
*/
annotate?: {
/**
* How long each annotation is displayed in milliseconds. Defaults to `500`.
*/
delay?: number;
};
};

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/client/screencast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class Screencast implements api.Screencast {
});
}

async start(onFrame: (frame: { data: Buffer }) => Promise<any>|any, options: { preferredSize?: { width: number, height: number } } = {}): Promise<DisposableStub> {
async start(onFrame: (frame: { data: Buffer }) => Promise<any>|any, options: { preferredSize?: { width: number, height: number }, annotate?: { delay?: number } } = {}): Promise<DisposableStub> {
if (this._onFrame)
throw new Error('Screencast is already started');
this._onFrame = onFrame;
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/client/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export class Video extends EventEmitter implements api.Video {
this._artifact = artifact;
}

async start(options: { path?: string, size?: { width: number, height: number } } = {}) {
const result = await this._page._channel.videoStart({ size: options.size });
async start(options: { path?: string, size?: { width: number, height: number }, annotate?: { delay?: number } } = {}) {
const result = await this._page._channel.videoStart({ size: options.size, annotate: options.annotate });
this._artifact = Artifact.from(result.artifact);
this._savePath = options.path;
return new DisposableStub(() => this.stop());
Expand Down
24 changes: 24 additions & 0 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,9 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({
width: tInt,
height: tInt,
})),
annotate: tOptional(tObject({
delay: tOptional(tInt),
})),
})),
strictSelectors: tOptional(tBoolean),
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
Expand Down Expand Up @@ -722,6 +725,9 @@ scheme.BrowserNewContextParams = tObject({
width: tInt,
height: tInt,
})),
annotate: tOptional(tObject({
delay: tOptional(tInt),
})),
})),
strictSelectors: tOptional(tBoolean),
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
Expand Down Expand Up @@ -793,6 +799,9 @@ scheme.BrowserNewContextForReuseParams = tObject({
width: tInt,
height: tInt,
})),
annotate: tOptional(tObject({
delay: tOptional(tInt),
})),
})),
strictSelectors: tOptional(tBoolean),
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
Expand Down Expand Up @@ -911,6 +920,9 @@ scheme.BrowserContextInitializer = tObject({
width: tInt,
height: tInt,
})),
annotate: tOptional(tObject({
delay: tOptional(tInt),
})),
})),
strictSelectors: tOptional(tBoolean),
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
Expand Down Expand Up @@ -1555,6 +1567,9 @@ scheme.PageStartScreencastParams = tObject({
width: tInt,
height: tInt,
})),
annotate: tOptional(tObject({
delay: tOptional(tInt),
})),
});
scheme.PageStartScreencastResult = tOptional(tObject({}));
scheme.PageStopScreencastParams = tOptional(tObject({}));
Expand All @@ -1564,6 +1579,9 @@ scheme.PageVideoStartParams = tObject({
width: tInt,
height: tInt,
})),
annotate: tOptional(tObject({
delay: tOptional(tInt),
})),
});
scheme.PageVideoStartResult = tObject({
artifact: tChannel(['Artifact']),
Expand Down Expand Up @@ -2653,6 +2671,9 @@ scheme.ElectronLaunchParams = tObject({
width: tInt,
height: tInt,
})),
annotate: tOptional(tObject({
delay: tOptional(tInt),
})),
})),
strictSelectors: tOptional(tBoolean),
timezoneId: tOptional(tString),
Expand Down Expand Up @@ -2888,6 +2909,9 @@ scheme.AndroidDeviceLaunchBrowserParams = tObject({
width: tInt,
height: tInt,
})),
annotate: tOptional(tObject({
delay: tOptional(tInt),
})),
})),
strictSelectors: tOptional(tBoolean),
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
this._screencastListener = (frame: ScreencastFrame) => {
this._dispatchEvent('screencastFrame', { data: frame.buffer });
};
await this._page.screencast.startScreencast(this._screencastListener, { quality: 90, width: size.width, height: size.height });
await this._page.screencast.startScreencast(this._screencastListener, { quality: 90, width: size.width, height: size.height, annotate: params.annotate });
}

async stopScreencast(params: channels.PageStopScreencastParams, progress?: Progress): Promise<channels.PageStopScreencastResult> {
Expand Down
Loading
Loading