Skip to content

Commit b7d6114

Browse files
committed
chore: resolve locator helper for mcp
1 parent 27ad487 commit b7d6114

File tree

8 files changed

+54
-49
lines changed

8 files changed

+54
-49
lines changed

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,8 @@ export class Locator implements api.Locator {
249249
return await this._frame._queryCount(this._selector);
250250
}
251251

252-
async _generateLocatorString(): Promise<string | null> {
253-
const { value } = await this._frame._channel.generateLocatorString({ selector: this._selector });
254-
return value === undefined ? null : value;
252+
async _resolveLocator(options: { sdkLanguage?: channels.SDKLanguage } = {}): Promise<{ resolvedSelector: string, sourceCode: string }> {
253+
return await this._frame._channel.resolveLocator({ selector: this._selector, sdkLanguage: options.sdkLanguage });
255254
}
256255

257256
async getAttribute(name: string, options?: TimeoutOptions): Promise<string | null> {

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ scheme.FormField = tObject({
213213
buffer: tBinary,
214214
})),
215215
});
216+
scheme.SDKLanguage = tEnum(['javascript', 'python', 'java', 'csharp']);
216217
scheme.APIRequestContextInitializer = tObject({
217218
tracing: tChannel(['Tracing']),
218219
});
@@ -367,7 +368,7 @@ scheme.LocalUtilsGlobToRegexResult = tObject({
367368
});
368369
scheme.RootInitializer = tOptional(tObject({}));
369370
scheme.RootInitializeParams = tObject({
370-
sdkLanguage: tEnum(['javascript', 'python', 'java', 'csharp']),
371+
sdkLanguage: tType('SDKLanguage'),
371372
});
372373
scheme.RootInitializeResult = tObject({
373374
playwright: tChannel(['Playwright']),
@@ -456,7 +457,7 @@ scheme.DebugControllerPausedEvent = tObject({
456457
});
457458
scheme.DebugControllerInitializeParams = tObject({
458459
codegenId: tString,
459-
sdkLanguage: tEnum(['javascript', 'python', 'java', 'csharp']),
460+
sdkLanguage: tType('SDKLanguage'),
460461
});
461462
scheme.DebugControllerInitializeResult = tOptional(tObject({}));
462463
scheme.DebugControllerSetReportStateChangedParams = tObject({
@@ -1659,11 +1660,13 @@ scheme.FrameFrameElementParams = tOptional(tObject({}));
16591660
scheme.FrameFrameElementResult = tObject({
16601661
element: tChannel(['ElementHandle']),
16611662
});
1662-
scheme.FrameGenerateLocatorStringParams = tObject({
1663+
scheme.FrameResolveLocatorParams = tObject({
16631664
selector: tString,
1665+
sdkLanguage: tOptional(tType('SDKLanguage')),
16641666
});
1665-
scheme.FrameGenerateLocatorStringResult = tObject({
1666-
value: tOptional(tString),
1667+
scheme.FrameResolveLocatorResult = tObject({
1668+
resolvedSelector: tString,
1669+
sourceCode: tString,
16671670
});
16681671
scheme.FrameHighlightParams = tObject({
16691672
selector: tString,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Br
178178
return { value: await this._frame.innerHTML(progress, params.selector, params) };
179179
}
180180

181-
async generateLocatorString(params: channels.FrameGenerateLocatorStringParams, progress: Progress): Promise<channels.FrameGenerateLocatorStringResult> {
182-
return { value: await this._frame.generateLocatorString(progress, params.selector) };
181+
async resolveLocator(params: channels.FrameResolveLocatorParams, progress: Progress): Promise<channels.FrameResolveLocatorResult> {
182+
return await this._frame.resolveLocator(progress, params);
183183
}
184184

185185
async getAttribute(params: channels.FrameGetAttributeParams, progress: Progress): Promise<channels.FrameGetAttributeResult> {

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,16 +1179,16 @@ export class Frame extends SdkObject {
11791179
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true /* performActionPreChecks */, handle => handle._blur(progress)));
11801180
}
11811181

1182-
async generateLocatorString(progress: Progress, selector: string): Promise<string | undefined> {
1183-
const element = await progress.race(this.selectors.query(selector));
1182+
async resolveLocator(progress: Progress, params: { selector: string, sdkLanguage?: channels.SDKLanguage }): Promise<{ resolvedSelector: string, sourceCode: string }> {
1183+
const element = await progress.race(this.selectors.query(params.selector));
11841184
if (!element)
1185-
throw new Error(`No element matching ${this._asLocator(selector)}`);
1185+
throw new Error(`No element matching ${params.selector}`);
11861186

11871187
const generated = await progress.race(element.evaluateInUtility(async ([injected, node]) => {
11881188
return injected.generateSelectorSimple(node as unknown as Element);
11891189
}, {}));
11901190
if (!generated)
1191-
throw new Error(`Unable to generate locator for ${this._asLocator(selector)}`);
1191+
throw new Error(`Unable to generate locator for ${params.selector}`);
11921192

11931193
let frame: Frame | null = element._frame;
11941194
const result = [generated];
@@ -1200,12 +1200,14 @@ export class Frame extends SdkObject {
12001200
}, {}));
12011201
frameElement.dispose();
12021202
if (generated === 'error:notconnected' || !generated)
1203-
throw new Error(`Unable to generate locator for ${this._asLocator(selector)}`);
1203+
throw new Error(`Unable to generate locator for ${params.selector}`);
12041204
result.push(generated);
12051205
}
12061206
frame = frame.parentFrame();
12071207
}
1208-
return asLocator(this._page.browserContext._browser.sdkLanguage(), result.reverse().join(' >> internal:control=enter-frame >> '));
1208+
const resolvedSelector = result.reverse().join(' >> internal:control=enter-frame >> ');
1209+
const sourceCode = asLocator(params.sdkLanguage ?? this._page.browserContext._browser.sdkLanguage(), resolvedSelector);
1210+
return { resolvedSelector, sourceCode };
12091211
}
12101212

12111213
async textContent(progress: Progress, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise<string | null> {

packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export const methodMetainfo = new Map<string, { internal?: boolean, title?: stri
155155
['Frame.fill', { title: 'Fill "{value}"', slowMo: true, snapshot: true, pausesBeforeInput: true, }],
156156
['Frame.focus', { title: 'Focus', slowMo: true, snapshot: true, }],
157157
['Frame.frameElement', { internal: true, }],
158-
['Frame.generateLocatorString', { internal: true, }],
158+
['Frame.resolveLocator', { internal: true, }],
159159
['Frame.highlight', { internal: true, }],
160160
['Frame.getAttribute', { internal: true, snapshot: true, }],
161161
['Frame.goto', { title: 'Navigate to "{url}"', slowMo: true, snapshot: true, }],

packages/protocol/src/channels.d.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ export type FormField = {
347347
},
348348
};
349349

350+
export type SDKLanguage = 'javascript' | 'python' | 'java' | 'csharp';
350351
// ----------- APIRequestContext -----------
351352
export type APIRequestContextInitializer = {
352353
tracing: TracingChannel,
@@ -608,7 +609,7 @@ export interface RootChannel extends RootEventTarget, Channel {
608609
initialize(params: RootInitializeParams, progress?: Progress): Promise<RootInitializeResult>;
609610
}
610611
export type RootInitializeParams = {
611-
sdkLanguage: 'javascript' | 'python' | 'java' | 'csharp',
612+
sdkLanguage: SDKLanguage,
612613
};
613614
export type RootInitializeOptions = {
614615

@@ -769,7 +770,7 @@ export type DebugControllerPausedEvent = {
769770
};
770771
export type DebugControllerInitializeParams = {
771772
codegenId: string,
772-
sdkLanguage: 'javascript' | 'python' | 'java' | 'csharp',
773+
sdkLanguage: SDKLanguage,
773774
};
774775
export type DebugControllerInitializeOptions = {
775776

@@ -2649,7 +2650,7 @@ export interface FrameChannel extends FrameEventTarget, Channel {
26492650
fill(params: FrameFillParams, progress?: Progress): Promise<FrameFillResult>;
26502651
focus(params: FrameFocusParams, progress?: Progress): Promise<FrameFocusResult>;
26512652
frameElement(params?: FrameFrameElementParams, progress?: Progress): Promise<FrameFrameElementResult>;
2652-
generateLocatorString(params: FrameGenerateLocatorStringParams, progress?: Progress): Promise<FrameGenerateLocatorStringResult>;
2653+
resolveLocator(params: FrameResolveLocatorParams, progress?: Progress): Promise<FrameResolveLocatorResult>;
26532654
highlight(params: FrameHighlightParams, progress?: Progress): Promise<FrameHighlightResult>;
26542655
getAttribute(params: FrameGetAttributeParams, progress?: Progress): Promise<FrameGetAttributeResult>;
26552656
goto(params: FrameGotoParams, progress?: Progress): Promise<FrameGotoResult>;
@@ -2905,14 +2906,16 @@ export type FrameFrameElementOptions = {};
29052906
export type FrameFrameElementResult = {
29062907
element: ElementHandleChannel,
29072908
};
2908-
export type FrameGenerateLocatorStringParams = {
2909+
export type FrameResolveLocatorParams = {
29092910
selector: string,
2911+
sdkLanguage?: SDKLanguage,
29102912
};
2911-
export type FrameGenerateLocatorStringOptions = {
2912-
2913+
export type FrameResolveLocatorOptions = {
2914+
sdkLanguage?: SDKLanguage,
29132915
};
2914-
export type FrameGenerateLocatorStringResult = {
2915-
value?: string,
2916+
export type FrameResolveLocatorResult = {
2917+
resolvedSelector: string,
2918+
sourceCode: string,
29162919
};
29172920
export type FrameHighlightParams = {
29182921
selector: string,

packages/protocol/src/protocol.yml

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,14 @@ FormField:
357357
mimeType: string?
358358
buffer: binary
359359

360+
SDKLanguage:
361+
type: enum
362+
literals:
363+
- javascript
364+
- python
365+
- java
366+
- csharp
367+
360368
APIRequestContext:
361369
type: interface
362370

@@ -774,13 +782,7 @@ Root:
774782
initialize:
775783
internal: true
776784
parameters:
777-
sdkLanguage:
778-
type: enum
779-
literals:
780-
- javascript
781-
- python
782-
- java
783-
- csharp
785+
sdkLanguage: SDKLanguage
784786
returns:
785787
playwright: Playwright
786788

@@ -883,13 +885,7 @@ DebugController:
883885
internal: true
884886
parameters:
885887
codegenId: string
886-
sdkLanguage:
887-
type: enum
888-
literals:
889-
- javascript
890-
- python
891-
- java
892-
- csharp
888+
sdkLanguage: SDKLanguage
893889

894890
setReportStateChanged:
895891
internal: true
@@ -2307,12 +2303,14 @@ Frame:
23072303
returns:
23082304
element: ElementHandle
23092305

2310-
generateLocatorString:
2306+
resolveLocator:
23112307
internal: true
23122308
parameters:
23132309
selector: string
2310+
sdkLanguage: SDKLanguage?
23142311
returns:
2315-
value: string?
2312+
resolvedSelector: string
2313+
sourceCode: string
23162314

23172315
highlight:
23182316
internal: true

tests/page/page-aria-snapshot-ai.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,21 @@ it('should stitch all frame snapshots', async ({ page, server }) => {
8989
expect(href3).toBe(server.PREFIX + '/frames/frame.html');
9090

9191
{
92-
const locator = await (page.locator('aria-ref=e1') as any)._generateLocatorString();
93-
expect(locator).toBe(`locator('body')`);
92+
const { sourceCode } = await (page.locator('aria-ref=e1') as any)._resolveLocator();
93+
expect(sourceCode).toBe(`locator('body')`);
9494
}
9595
{
96-
const locator = await (page.locator('aria-ref=f3e2') as any)._generateLocatorString();
97-
expect(locator).toBe(`locator('iframe[name="2frames"]').contentFrame().locator('iframe[name="dos"]').contentFrame().getByText('Hi, I\\'m frame')`);
96+
const { sourceCode } = await (page.locator('aria-ref=f3e2') as any)._resolveLocator();
97+
expect(sourceCode).toBe(`locator('iframe[name="2frames"]').contentFrame().locator('iframe[name="dos"]').contentFrame().getByText('Hi, I\\'m frame')`);
9898
}
9999
{
100100
// Should tolerate .describe().
101-
const locator = await (page.locator('aria-ref=f2e2').describe('foo bar') as any)._generateLocatorString();
102-
expect(locator).toBe(`locator('iframe[name=\"2frames\"]').contentFrame().locator('iframe[name=\"uno\"]').contentFrame().getByText('Hi, I\\'m frame')`);
101+
const { sourceCode } = await (page.locator('aria-ref=f2e2').describe('foo bar') as any)._resolveLocator();
102+
expect(sourceCode).toBe(`locator('iframe[name=\"2frames\"]').contentFrame().locator('iframe[name=\"uno\"]').contentFrame().getByText('Hi, I\\'m frame')`);
103103
}
104104
{
105-
const error = await (page.locator('aria-ref=e1000') as any)._generateLocatorString().catch(e => e);
106-
expect(error.message).toContain(`No element matching locator('aria-ref=e1000')`);
105+
const error = await (page.locator('aria-ref=e1000') as any)._resolveLocator().catch(e => e);
106+
expect(error.message).toContain(`No element matching aria-ref=e1000`);
107107
}
108108
});
109109

0 commit comments

Comments
 (0)