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
13 changes: 6 additions & 7 deletions docs/src/api/class-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -3857,23 +3857,22 @@ Handler function to route the WebSocket.
Handler function to route the WebSocket.


## method: Page.screencast
## property: Page.screencast
* since: v1.59
* langs: js
- returns: <[Screencast]>
- type: <[Screencast]>

Returns the [Screencast] object associated with this page.
[Screencast] object associated with this page.

**Usage**

```js
const screencast = page.screencast();
screencast.on('screencastFrame', data => {
page.screencast.on('screencastFrame', data => {
console.log('received frame, jpeg size:', data.length);
});
await screencast.start();
await page.screencast.start();
// ... perform actions ...
await screencast.stop();
await page.screencast.stop();
```


Expand Down
4 changes: 2 additions & 2 deletions docs/src/api/class-screencast.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Emitted for each captured JPEG screencast frame while the screencast is running.
**Usage**

```js
const screencast = page.screencast();
const screencast = page.screencast;
screencast.on('screencastframe', ({ data, width, height }) => {
console.log(`frame ${width}x${height}, jpeg size: ${data.length}`);
require('fs').writeFileSync('frame.jpg', data);
Expand All @@ -32,7 +32,7 @@ Starts capturing screencast frames. Frames are emitted as [`event: Screencast.sc
**Usage**

```js
const screencast = page.screencast();
const screencast = page.screencast;
screencast.on('screencastframe', ({ data, width, height }) => {
console.log(`frame ${width}x${height}, size: ${data.length}`);
});
Expand Down
2 changes: 1 addition & 1 deletion docs/src/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ jobs:
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run changed Playwright tests
run: npx playwright test --only-changed=$GITHUB_BASE_REF
run: npx playwright test --only-changed=origin/$GITHUB_BASE_REF
if: github.event_name == 'pull_request'
- name: Run Playwright tests
run: npx playwright test
Expand Down
7 changes: 7 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,13 @@ export default [
}],
},
],
"no-restricted-syntax": [
"error",
{
selector: "TSAsExpression > TSAnyKeyword",
message: "Avoid 'as any' — risk of accidentally casting to client interfaces. Use a precise type or add an eslint-disable with justification.",
},
],
},
},
{
Expand Down
43 changes: 21 additions & 22 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4157,24 +4157,6 @@ export interface Page {
*/
routeWebSocket(url: string|RegExp|URLPattern|((url: URL) => boolean), handler: ((websocketroute: WebSocketRoute) => Promise<any>|any)): Promise<void>;

/**
* Returns the [Screencast](https://playwright.dev/docs/api/class-screencast) object associated with this page.
*
* **Usage**
*
* ```js
* const screencast = page.screencast();
* screencast.on('screencastFrame', data => {
* console.log('received frame, jpeg size:', data.length);
* });
* await screencast.start();
* // ... perform actions ...
* await screencast.stop();
* ```
*
*/
screencast(): Screencast;

/**
* Returns the buffer with the captured screenshot.
* @param options
Expand Down Expand Up @@ -5311,6 +5293,23 @@ export interface Page {
*/
request: APIRequestContext;

/**
* [Screencast](https://playwright.dev/docs/api/class-screencast) object associated with this page.
*
* **Usage**
*
* ```js
* page.screencast.on('screencastFrame', data => {
* console.log('received frame, jpeg size:', data.length);
* });
* await page.screencast.start();
* // ... perform actions ...
* await page.screencast.stop();
* ```
*
*/
screencast: Screencast;

touchscreen: Touchscreen;

[Symbol.asyncDispose](): Promise<void>;
Expand Down Expand Up @@ -21648,7 +21647,7 @@ export interface Screencast {
* **Usage**
*
* ```js
* const screencast = page.screencast();
* const screencast = page.screencast;
* screencast.on('screencastframe', ({ data, width, height }) => {
* console.log(`frame ${width}x${height}, jpeg size: ${data.length}`);
* require('fs').writeFileSync('frame.jpg', data);
Expand Down Expand Up @@ -21682,7 +21681,7 @@ export interface Screencast {
* **Usage**
*
* ```js
* const screencast = page.screencast();
* const screencast = page.screencast;
* screencast.on('screencastframe', ({ data, width, height }) => {
* console.log(`frame ${width}x${height}, jpeg size: ${data.length}`);
* require('fs').writeFileSync('frame.jpg', data);
Expand Down Expand Up @@ -21726,7 +21725,7 @@ export interface Screencast {
* **Usage**
*
* ```js
* const screencast = page.screencast();
* const screencast = page.screencast;
* screencast.on('screencastframe', ({ data, width, height }) => {
* console.log(`frame ${width}x${height}, jpeg size: ${data.length}`);
* require('fs').writeFileSync('frame.jpg', data);
Expand All @@ -21752,7 +21751,7 @@ export interface Screencast {
* **Usage**
*
* ```js
* const screencast = page.screencast();
* const screencast = page.screencast;
* screencast.on('screencastframe', ({ data, width, height }) => {
* console.log(`frame ${width}x${height}, size: ${data.length}`);
* });
Expand Down
8 changes: 2 additions & 6 deletions packages/playwright-core/src/client/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,12 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
readonly request: APIRequestContext;
readonly touchscreen: Touchscreen;
readonly clock: Clock;
readonly screencast: Screencast;


readonly _bindings = new Map<string, (source: structs.BindingSource, ...args: any[]) => any>();
readonly _timeoutSettings: TimeoutSettings;
private _video: Video;
private _screencast: Screencast;
readonly _opener: Page | null;
private _closeReason: string | undefined;
_closeWasCalled: boolean = false;
Expand Down Expand Up @@ -135,7 +135,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
this._closed = initializer.isClosed;
this._opener = Page.fromNullable(initializer.opener);
this._video = new Video(this, this._connection, initializer.video ? Artifact.from(initializer.video) : undefined);
this._screencast = new Screencast(this);
this.screencast = new Screencast(this);

this._channel.on('bindingCall', ({ binding }) => this._onBinding(BindingCall.from(binding)));
this._channel.on('close', () => this._onClose());
Expand Down Expand Up @@ -285,10 +285,6 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
return this._video;
}

screencast(): Screencast {
return this._screencast;
}

async pickLocator(): Promise<Locator> {
const { selector } = await this._channel.pickLocator({});
return this.locator(selector);
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/server/bidi/bidiBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ export class BidiBrowserContext extends BrowserContext {
await this._browser._browserSession.send('browser.removeUserContext', {
userContext: this._browserContextId
});
await Promise.all(this._bidiPages().map(bidiPage => bidiPage._page.closedPromise));
this._browser._contexts.delete(this._browserContextId);
}

Expand Down
3 changes: 2 additions & 1 deletion packages/playwright-core/src/server/bidi/bidiPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ export class BidiPage implements PageDelegate {

const callFrame = params.stackTrace?.callFrames[0];
const location = callFrame ?? { url: '', lineNumber: 1, columnNumber: 1 };
this._page.addConsoleMessage(null, entry.method, entry.args.map(arg => createHandle(context, arg)), location, undefined, params.timestamp);
const type = entry.method === 'warn' ? 'warning' : entry.method;
this._page.addConsoleMessage(null, type, entry.args.map(arg => createHandle(context, arg)), location, undefined, params.timestamp);
}

private async _onFileDialogOpened(params: bidi.Input.FileDialogInfo) {
Expand Down
13 changes: 11 additions & 2 deletions packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1566,8 +1566,17 @@ export class Frame extends SdkObject<FrameEventMap> {
}

async title(): Promise<string> {
const context = await this._utilityContext();
return context.evaluate(() => document.title);
try {
return await this.raceAgainstEvaluationStallingEvents(async () => {
const context = await this._utilityContext();
return await context.evaluate(() => document.title);
});
} catch {
const url = this.pendingDocument()?.request?.url();
if (url)
return `Loading ${url}`;
return '';
}
}

async rafrafTimeout(progress: Progress, timeout: number): Promise<void> {
Expand Down
6 changes: 3 additions & 3 deletions packages/playwright-core/src/server/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export class Page extends SdkObject<PageEventMap> {
static Events = PageEvent;

private _closedState: 'open' | 'closing' | 'closed' = 'open';
private _closedPromise = new ManualPromise<void>();
readonly closedPromise = new ManualPromise<void>();
private _initialized: Page | Error | undefined;
private _initializedPromise = new ManualPromise<Page | Error>();
private _consoleMessages: ConsoleMessage[] = [];
Expand Down Expand Up @@ -284,7 +284,7 @@ export class Page extends SdkObject<PageEventMap> {
this._closedState = 'closed';
this.emit(Page.Events.Close);
this.browserContext.emit(BrowserContext.Events.PageClosed, this);
this._closedPromise.resolve();
this.closedPromise.resolve();
this.instrumentation.onPageClose(this);
this.openScope.close(new TargetClosedError(this.closeReason()));
}
Expand Down Expand Up @@ -797,7 +797,7 @@ export class Page extends SdkObject<PageEventMap> {
await this.delegate.closePage(runBeforeUnload).catch(e => debugLogger.log('error', e));
}
if (!runBeforeUnload)
await this._closedPromise;
await this.closedPromise;
}

isClosed(): boolean {
Expand Down
7 changes: 4 additions & 3 deletions packages/playwright-core/src/tools/backend/browserBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,17 @@ export class BrowserBackend implements ServerBackend {
await this._context?.dispose().catch(e => debug('pw:tools:error')(e));
}

async callTool(name: string, rawArguments: mcpServer.CallToolRequest['params']['arguments']) {
async callTool(name: string, rawArguments: mcpServer.CallToolRequest['params']['arguments'] & { _meta?: Record<string, any> } = {}): Promise<mcpServer.CallToolResult> {
const tool = this._tools.find(tool => tool.schema.name === name)!;
if (!tool) {
return {
content: [{ type: 'text' as const, text: `### Error\nTool "${name}" not found` }],
isError: true,
};
}
const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {}) as any;
const cwd = rawArguments?._meta && typeof rawArguments?._meta === 'object' && (rawArguments._meta as any)?.cwd;
// eslint-disable-next-line no-restricted-syntax
const parsedArguments = tool.schema.inputSchema.parse(rawArguments) as any;
const cwd = rawArguments._meta?.cwd;
const context = this._context!;
const response = new Response(context, name, parsedArguments, cwd);
context.setRunningTool(name);
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/tools/backend/evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const evaluate = defineTabTool({
}

await tab.waitForCompletion(async () => {
// eslint-disable-next-line no-restricted-syntax
const func = new Function() as any;
func.toString = () => params.function;
const result = locator?.locator ? await locator?.locator.evaluate(func) : await tab.page.evaluate(func);
Expand Down
4 changes: 1 addition & 3 deletions packages/playwright-core/src/tools/backend/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,7 @@ function trimMiddle(text: string, maxLength: number) {
* Replaces lone surrogates with U+FFFD using String.prototype.toWellFormed().
*/
function sanitizeUnicode(text: string): string {
if ((String.prototype as any).toWellFormed)
return text.toWellFormed();
return text;
return text.toWellFormed?.() ?? text;
}

function parseSections(text: string): Map<string, string> {
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/tools/backend/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export function scaleImageToFitMessage(buffer: Buffer, imageType: 'png' | 'jpeg'
const width = image.width * shrink | 0;
const height = image.height * shrink | 0;
const scaledImage = scaleImageToSize(image, { width, height });
// eslint-disable-next-line no-restricted-syntax
return imageType === 'png' ? PNG.sync.write(scaledImage as any) : jpegjs.encode(scaledImage, 80).data;
}

Expand Down
5 changes: 1 addition & 4 deletions packages/playwright-core/src/tools/backend/sessionLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,7 @@ export class SessionLog {
}

logResponse(toolName: string, toolArgs: Record<string, any>, responseObject: any) {
const parsed = parseResponse(responseObject) as any;
if (parsed)
delete parsed.text;

const parsed = { ...parseResponse(responseObject), text: undefined };
const lines: string[] = [''];
lines.push(
`### Tool call: ${toolName}`,
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/tools/backend/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export class Tab extends EventEmitter<TabEventsInterface> {
void this._downloadStarted(download);
}),
];
// eslint-disable-next-line no-restricted-syntax
(page as any)[tabSymbol] = this;
const wallTime = Date.now();
this._consoleLog = new LogFile(this.context, wallTime, 'console', 'Console');
Expand All @@ -147,6 +148,7 @@ export class Tab extends EventEmitter<TabEventsInterface> {
}

static forPage(page: playwright.Page): Tab | undefined {
// eslint-disable-next-line no-restricted-syntax
return (page as any)[tabSymbol];
}

Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/tools/backend/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const tracingStart = defineTool({
response.addFileLink('Action log', `${tracesDir}/${name}.trace`);
response.addFileLink('Network log', `${tracesDir}/${name}.network`);
response.addFileLink('Resources', `${tracesDir}/resources`);
// eslint-disable-next-line no-restricted-syntax
(browserContext.tracing as any)[traceLegendSymbol] = { tracesDir, name };
},
});
Expand All @@ -61,6 +62,7 @@ const tracingStop = defineTool({
handle: async (context, params, response) => {
const browserContext = await context.ensureBrowserContext();
await browserContext.tracing.stop();
// eslint-disable-next-line no-restricted-syntax
const traceLegend = (browserContext.tracing as any)[traceLegendSymbol];
response.addTextResult(`Trace recording stopped.`);
response.addFileLink('Trace', `${traceLegend.tracesDir}/${traceLegend.name}.trace`);
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/tools/backend/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ export function eventWaiter<T>(page: playwright.Page, event: string, timeout: nu
const disposables: (() => void)[] = [];

const eventPromise = new Promise<T | undefined>((resolve, reject) => {
// eslint-disable-next-line no-restricted-syntax
page.on(event as any, resolve as any);
// eslint-disable-next-line no-restricted-syntax
disposables.push(() => page.off(event as any, resolve as any));
});

Expand Down
3 changes: 2 additions & 1 deletion packages/playwright-core/src/tools/backend/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { z } from '../../mcpBundle';
import { escapeWithQuotes } from '../../utils/isomorphic/stringUtils';

import { defineTabTool } from './tool';
import type * as playwright from '../../..';

const verifyElement = defineTabTool({
capability: 'testing',
Expand All @@ -34,7 +35,7 @@ const verifyElement = defineTabTool({

handle: async (tab, params, response) => {
for (const frame of tab.page.frames()) {
const locator = frame.getByRole(params.role as any, { name: params.accessibleName });
const locator = frame.getByRole(params.role as Parameters<playwright.Frame['getByRole']>[0], { name: params.accessibleName });
if (await locator.count() > 0) {
const resolved = await locator.toCode();
response.addCode(`await expect(page.${resolved}).toBeVisible();`);
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/tools/cli-daemon/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function zodParse(schema: zodType.ZodAny, data: unknown, type: 'option' | 'argum
return schema.parse(data);
} catch (e) {
throw new Error((e as zodType.ZodError).issues.map(issue => {
const keys: string[] = (issue as any).keys || [''];
const keys: string[] = issue.code === 'unrecognized_keys' ? issue.keys : [''];
const props = keys.map(key => [...issue.path, key].filter(Boolean).join('.'));
return props.map(prop => {
const label = type === 'option' ? `'--${prop}' option` : `'${prop}' argument`;
Expand Down
6 changes: 3 additions & 3 deletions packages/playwright-core/src/tools/cli-daemon/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,11 @@ const mouseWheel = declareCommand({
description: 'Scroll mouse wheel',
category: 'mouse',
args: z.object({
dx: numberArg.describe('Y delta'),
dy: numberArg.describe('X delta'),
dx: numberArg.describe('X delta'),
dy: numberArg.describe('Y delta'),
}),
toolName: 'browser_mouse_wheel',
toolParams: ({ dx: deltaY, dy: deltaX }) => ({ deltaY, deltaX }),
toolParams: ({ dx: deltaX, dy: deltaY }) => ({ deltaX, deltaY }),
});

// Core
Expand Down
Loading
Loading