Skip to content

Commit 472741b

Browse files
authored
chore: resolve locator helper for mcp (microsoft#36680)
1 parent 27ad487 commit 472741b

File tree

8 files changed

+51
-47
lines changed

8 files changed

+51
-47
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 _resolveSelector(): Promise<{ resolvedSelector: string }> {
253+
return await this._frame._channel.resolveSelector({ selector: this._selector });
255254
}
256255

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

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

Lines changed: 6 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,11 @@ scheme.FrameFrameElementParams = tOptional(tObject({}));
16591660
scheme.FrameFrameElementResult = tObject({
16601661
element: tChannel(['ElementHandle']),
16611662
});
1662-
scheme.FrameGenerateLocatorStringParams = tObject({
1663+
scheme.FrameResolveSelectorParams = tObject({
16631664
selector: tString,
16641665
});
1665-
scheme.FrameGenerateLocatorStringResult = tObject({
1666-
value: tOptional(tString),
1666+
scheme.FrameResolveSelectorResult = tObject({
1667+
resolvedSelector: tString,
16671668
});
16681669
scheme.FrameHighlightParams = tObject({
16691670
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 resolveSelector(params: channels.FrameResolveSelectorParams, progress: Progress): Promise<channels.FrameResolveSelectorResult> {
182+
return await this._frame.resolveSelector(progress, params.selector);
183183
}
184184

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

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

Lines changed: 6 additions & 5 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> {
1182+
async resolveSelector(progress: Progress, selector: string): Promise<{ resolvedSelector: string }> {
11831183
const element = await progress.race(this.selectors.query(selector));
11841184
if (!element)
1185-
throw new Error(`No element matching ${this._asLocator(selector)}`);
1185+
throw new Error(`No element matching ${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 ${selector}`);
11921192

11931193
let frame: Frame | null = element._frame;
11941194
const result = [generated];
@@ -1200,12 +1200,13 @@ 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 ${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+
return { resolvedSelector };
12091210
}
12101211

12111212
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.resolveSelector', { 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: 8 additions & 7 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+
resolveSelector(params: FrameResolveSelectorParams, progress?: Progress): Promise<FrameResolveSelectorResult>;
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,14 @@ export type FrameFrameElementOptions = {};
29052906
export type FrameFrameElementResult = {
29062907
element: ElementHandleChannel,
29072908
};
2908-
export type FrameGenerateLocatorStringParams = {
2909+
export type FrameResolveSelectorParams = {
29092910
selector: string,
29102911
};
2911-
export type FrameGenerateLocatorStringOptions = {
2912+
export type FrameResolveSelectorOptions = {
29122913

29132914
};
2914-
export type FrameGenerateLocatorStringResult = {
2915-
value?: string,
2915+
export type FrameResolveSelectorResult = {
2916+
resolvedSelector: string,
29162917
};
29172918
export type FrameHighlightParams = {
29182919
selector: string,

packages/protocol/src/protocol.yml

Lines changed: 12 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,12 @@ Frame:
23072303
returns:
23082304
element: ElementHandle
23092305

2310-
generateLocatorString:
2306+
resolveSelector:
23112307
internal: true
23122308
parameters:
23132309
selector: string
23142310
returns:
2315-
value: string?
2311+
resolvedSelector: string
23162312

23172313
highlight:
23182314
internal: true

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17+
// @ts-ignore
18+
import { asLocator } from 'playwright-core/lib/utils';
19+
1720
import { test as it, expect, unshift } from './pageTest';
1821

1922
function snapshotForAI(page: any): Promise<string> {
@@ -89,21 +92,24 @@ it('should stitch all frame snapshots', async ({ page, server }) => {
8992
expect(href3).toBe(server.PREFIX + '/frames/frame.html');
9093

9194
{
92-
const locator = await (page.locator('aria-ref=e1') as any)._generateLocatorString();
93-
expect(locator).toBe(`locator('body')`);
95+
const { resolvedSelector } = await (page.locator('aria-ref=e1') as any)._resolveSelector();
96+
const sourceCode = asLocator('javascript', resolvedSelector);
97+
expect(sourceCode).toBe(`locator('body')`);
9498
}
9599
{
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')`);
100+
const { resolvedSelector } = await (page.locator('aria-ref=f3e2') as any)._resolveSelector();
101+
const sourceCode = asLocator('javascript', resolvedSelector);
102+
expect(sourceCode).toBe(`locator('iframe[name="2frames"]').contentFrame().locator('iframe[name="dos"]').contentFrame().getByText('Hi, I\\'m frame')`);
98103
}
99104
{
100105
// 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')`);
106+
const { resolvedSelector } = await (page.locator('aria-ref=f2e2').describe('foo bar') as any)._resolveSelector();
107+
const sourceCode = asLocator('javascript', resolvedSelector);
108+
expect(sourceCode).toBe(`locator('iframe[name=\"2frames\"]').contentFrame().locator('iframe[name=\"uno\"]').contentFrame().getByText('Hi, I\\'m frame')`);
103109
}
104110
{
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')`);
111+
const error = await (page.locator('aria-ref=e1000') as any)._resolveSelector().catch(e => e);
112+
expect(error.message).toContain(`No element matching aria-ref=e1000`);
107113
}
108114
});
109115

0 commit comments

Comments
 (0)