Skip to content

Commit bd2fdd0

Browse files
authored
chore: land experimental tools (#34503)
1 parent eaaef29 commit bd2fdd0

27 files changed

+1362
-16
lines changed

package-lock.json

Lines changed: 292 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/playwright-core/src/client/elementHandle.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements
6262
return Frame.fromNullable((await this._elementChannel.contentFrame()).frame);
6363
}
6464

65+
async _generateLocatorString(): Promise<string | null> {
66+
const value = (await this._elementChannel.generateLocatorString()).value;
67+
return value === undefined ? null : value;
68+
}
69+
6570
async getAttribute(name: string): Promise<string | null> {
6671
const value = (await this._elementChannel.getAttribute({ name })).value;
6772
return value === undefined ? null : value;

packages/playwright-core/src/client/locator.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ export class Locator implements api.Locator {
236236
return await this._frame._queryCount(this._selector);
237237
}
238238

239+
async _generateLocatorString(): Promise<string | null> {
240+
return await this._withElement(h => h._generateLocatorString());
241+
}
242+
239243
async getAttribute(name: string, options?: TimeoutOptions): Promise<string | null> {
240244
return await this._frame.getAttribute(this._selector, name, { strict: true, ...options });
241245
}
@@ -288,8 +292,8 @@ export class Locator implements api.Locator {
288292
return await this._withElement((h, timeout) => h.screenshot({ ...options, timeout }), options.timeout);
289293
}
290294

291-
async ariaSnapshot(options?: TimeoutOptions): Promise<string> {
292-
const result = await this._frame._channel.ariaSnapshot({ ...options, selector: this._selector });
295+
async ariaSnapshot(options?: { _id?: boolean, _mode?: 'raw' | 'regex' } & TimeoutOptions): Promise<string> {
296+
const result = await this._frame._channel.ariaSnapshot({ ...options, id: options?._id, mode: options?._mode, selector: this._selector });
293297
return result.snapshot;
294298
}
295299

packages/playwright-core/src/protocol/validator.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,8 @@ scheme.FrameAddStyleTagResult = tObject({
14291429
});
14301430
scheme.FrameAriaSnapshotParams = tObject({
14311431
selector: tString,
1432+
id: tOptional(tBoolean),
1433+
mode: tOptional(tEnum(['raw', 'regex'])),
14321434
timeout: tOptional(tNumber),
14331435
});
14341436
scheme.FrameAriaSnapshotResult = tObject({
@@ -1925,6 +1927,10 @@ scheme.ElementHandleFillParams = tObject({
19251927
scheme.ElementHandleFillResult = tOptional(tObject({}));
19261928
scheme.ElementHandleFocusParams = tOptional(tObject({}));
19271929
scheme.ElementHandleFocusResult = tOptional(tObject({}));
1930+
scheme.ElementHandleGenerateLocatorStringParams = tOptional(tObject({}));
1931+
scheme.ElementHandleGenerateLocatorStringResult = tObject({
1932+
value: tOptional(tString),
1933+
});
19281934
scheme.ElementHandleGetAttributeParams = tObject({
19291935
name: tString,
19301936
});

packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements chann
6363
return { frame: frame ? FrameDispatcher.from(this._browserContextDispatcher(), frame) : undefined };
6464
}
6565

66+
async generateLocatorString(params: channels.ElementHandleGenerateLocatorStringParams, metadata: CallMetadata): Promise<channels.ElementHandleGenerateLocatorStringResult> {
67+
return { value: await this._elementHandle.generateLocatorString() };
68+
}
69+
6670
async getAttribute(params: channels.ElementHandleGetAttributeParams, metadata: CallMetadata): Promise<channels.ElementHandleGetAttributeResult> {
6771
const value = await this._elementHandle.getAttribute(metadata, params.name);
6872
return { value: value === null ? undefined : value };

packages/playwright-core/src/server/dom.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import type { Progress } from './progress';
2828
import { ProgressController } from './progress';
2929
import type * as types from './types';
3030
import type { TimeoutOptions } from '../common/types';
31-
import { isUnderTest } from '../utils';
31+
import { asLocator, isUnderTest } from '../utils';
3232
import { prepareFilesForUpload } from './fileUploadUtils';
3333

3434
export type InputFilesItems = {
@@ -185,6 +185,15 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
185185
return this._page._delegate.getContentFrame(this);
186186
}
187187

188+
async generateLocatorString(): Promise<string | undefined> {
189+
const selector = await this.evaluateInUtility(async ([injected, node]) => {
190+
return injected.generateSelectorSimple(node as unknown as Element);
191+
}, {});
192+
if (selector === 'error:notconnected')
193+
return;
194+
return asLocator('javascript', selector);
195+
}
196+
188197
async getAttribute(metadata: CallMetadata, name: string): Promise<string | null> {
189198
return this._frame.getAttribute(metadata, ':scope', name, {}, this);
190199
}
@@ -799,8 +808,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
799808
return this._page._delegate.getBoundingBox(this);
800809
}
801810

802-
async ariaSnapshot(): Promise<string> {
803-
return await this.evaluateInUtility(([injected, element]) => injected.ariaSnapshot(element), {});
811+
async ariaSnapshot(options: { id?: boolean, mode?: 'raw' | 'regex' }): Promise<string> {
812+
return await this.evaluateInUtility(([injected, element, options]) => injected.ariaSnapshot(element, options), options);
804813
}
805814

806815
async screenshot(metadata: CallMetadata, options: ScreenshotOptions & TimeoutOptions = {}): Promise<Buffer> {

packages/playwright-core/src/server/frames.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,10 +1408,10 @@ export class Frame extends SdkObject {
14081408
});
14091409
}
14101410

1411-
async ariaSnapshot(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}): Promise<string> {
1411+
async ariaSnapshot(metadata: CallMetadata, selector: string, options: { id?: boolean, mode?: 'raw' | 'regex' } & types.TimeoutOptions = {}): Promise<string> {
14121412
const controller = new ProgressController(metadata, this);
14131413
return controller.run(async progress => {
1414-
return await this._retryWithProgressIfNotConnected(progress, selector, true /* strict */, true /* performActionPreChecks */, handle => handle.ariaSnapshot());
1414+
return await this._retryWithProgressIfNotConnected(progress, selector, true /* strict */, true /* performActionPreChecks */, handle => handle.ariaSnapshot(options));
14151415
}, this._page._timeoutSettings.timeout(options));
14161416
}
14171417

packages/playwright-core/src/server/injected/consoleApi.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,8 @@ class ConsoleAPI {
8585
inspect: (selector: string) => this._inspect(selector),
8686
selector: (element: Element) => this._selector(element),
8787
generateLocator: (element: Element, language?: Language) => this._generateLocator(element, language),
88-
ariaSnapshot: (element?: Element) => {
89-
const snapshot = this._injectedScript.ariaSnapshot(element || this._injectedScript.document.body);
90-
// eslint-disable-next-line no-console
91-
console.log(snapshot);
88+
ariaSnapshot: (element?: Element, options?: { id?: boolean }) => {
89+
return this._injectedScript.ariaSnapshot(element || this._injectedScript.document.body, options);
9290
},
9391
resume: () => this._resume(),
9492
...new Locator(injectedScript, ''),

packages/playwright-core/src/server/injected/injectedScript.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export class InjectedScript {
7272
// eslint-disable-next-line no-restricted-globals
7373
readonly window: Window & typeof globalThis;
7474
readonly document: Document;
75+
private _ariaElementById: Map<number, Element> | undefined;
7576

7677
// Recorder must use any external dependencies through InjectedScript.
7778
// Otherwise it will end up with a copy of all modules it uses, and any
@@ -130,6 +131,7 @@ export class InjectedScript {
130131
this._engines.set('internal:attr', this._createNamedAttributeEngine());
131132
this._engines.set('internal:testid', this._createNamedAttributeEngine());
132133
this._engines.set('internal:role', createRoleEngine(true));
134+
this._engines.set('internal:aria-id', this._createAriaIdEngine());
133135

134136
for (const { name, engine } of customEngines)
135137
this._engines.set(name, engine);
@@ -221,7 +223,8 @@ export class InjectedScript {
221223
if (node.nodeType !== Node.ELEMENT_NODE)
222224
throw this.createStacklessError('Can only capture aria snapshot of Element nodes.');
223225
const ariaSnapshot = generateAriaTree(node as Element);
224-
return renderAriaTree(ariaSnapshot.root, options);
226+
this._ariaElementById = ariaSnapshot.elements;
227+
return renderAriaTree(ariaSnapshot.root, { ...options, ids: options?.id ? ariaSnapshot.ids : undefined });
225228
}
226229

227230
ariaSnapshotAsObject(node: Node): AriaSnapshot {
@@ -609,6 +612,15 @@ export class InjectedScript {
609612
return result;
610613
}
611614

615+
_createAriaIdEngine() {
616+
const queryAll = (root: SelectorRoot, selector: string): Element[] => {
617+
const ariaId = parseInt(selector, 10);
618+
const result = this._ariaElementById?.get(ariaId);
619+
return result && result.isConnected ? [result] : [];
620+
};
621+
return { queryAll };
622+
}
623+
612624
elementState(node: Node, state: ElementStateWithoutStable): ElementStateQueryResult {
613625
const element = this.retarget(node, ['visible', 'hidden'].includes(state) ? 'none' : 'follow-label');
614626
if (!element || !element.isConnected) {

packages/playwright-core/src/server/selectors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class Selectors {
3939
'internal:has', 'internal:has-not',
4040
'internal:has-text', 'internal:has-not-text',
4141
'internal:and', 'internal:or', 'internal:chain',
42-
'role', 'internal:attr', 'internal:label', 'internal:text', 'internal:role', 'internal:testid',
42+
'role', 'internal:attr', 'internal:label', 'internal:text', 'internal:role', 'internal:testid', 'internal:aria-id'
4343
]);
4444
this._builtinEnginesInMainWorld = new Set([
4545
'_react', '_vue',

packages/playwright-tools/.npmignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
**/*
2+
README.md
3+
LICENSE
4+
!lib/*
5+
!browser.js
6+
!browser.d.ts
7+
!computer-20241022.js
8+
!computer-20241022.d.ts

packages/playwright-tools/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
> **BEWARE** This package is EXPERIMENTAL and does not respect semver.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import type playwright from 'playwright';
18+
import { ToolDeclaration, JSONSchemaType } from './types';
19+
20+
export type ToolResult = {
21+
error?: string;
22+
code: Array<string>;
23+
snapshot: string;
24+
}
25+
26+
export type ToolCall = (page: playwright.Page, tool: string, parameters: { [key: string]: JSONSchemaType; }) => Promise<ToolResult>;
27+
28+
export const schema: ToolDeclaration[];
29+
export const call: ToolCall;
30+
export const snapshot: (page) => Promise<string>;

packages/playwright-tools/browser.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const { schema, call, snapshot } = require('./lib/tools/browser');
18+
19+
module.exports = { schema, call, snapshot };
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import type playwright from 'playwright';
18+
import { JSONSchemaType } from './types';
19+
20+
export type ToolResult = {
21+
output?: string;
22+
error?: string;
23+
base64_image?: string;
24+
};
25+
26+
export type ToolCall = (page: playwright.Page, tool: string, parameters: { [key: string]: JSONSchemaType; }) => Promise<ToolResult>;
27+
28+
export const call: ToolCall;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const { call } = require('./lib/tools/computer-20241022');
18+
19+
module.exports = { call };
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@playwright/experimental-tools",
3+
"version": "1.51.0-next",
4+
"description": "Playwright Tools for AI",
5+
"repository": {
6+
"type": "git",
7+
"url": "git+https://github.com/microsoft/playwright.git"
8+
},
9+
"homepage": "https://playwright.dev",
10+
"engines": {
11+
"node": ">=18"
12+
},
13+
"author": {
14+
"name": "Microsoft Corporation"
15+
},
16+
"license": "Apache-2.0",
17+
"exports": {
18+
"./browser": {
19+
"types": "./browser.d.ts",
20+
"default": "./browser.js"
21+
},
22+
"./computer-20241022": {
23+
"types": "./computer-20241022.d.ts",
24+
"default": "./computer-20241022.js"
25+
},
26+
"./package.json": "./package.json"
27+
},
28+
"dependencies": {
29+
"playwright": "1.51.0-next"
30+
},
31+
"devDependencies": {
32+
"@anthropic-ai/sdk": "^0.33.1",
33+
"openai": "^4.79.1"
34+
}
35+
}

0 commit comments

Comments
 (0)