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
5 changes: 2 additions & 3 deletions packages/playwright-core/src/client/locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,8 @@ export class Locator implements api.Locator {
return await this._frame._queryCount(this._selector);
}

async _generateLocatorString(): Promise<string | null> {
const { value } = await this._frame._channel.generateLocatorString({ selector: this._selector });
return value === undefined ? null : value;
async _resolveSelector(): Promise<{ resolvedSelector: string }> {
return await this._frame._channel.resolveSelector({ selector: this._selector });
}

async getAttribute(name: string, options?: TimeoutOptions): Promise<string | null> {
Expand Down
11 changes: 6 additions & 5 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ scheme.FormField = tObject({
buffer: tBinary,
})),
});
scheme.SDKLanguage = tEnum(['javascript', 'python', 'java', 'csharp']);
scheme.APIRequestContextInitializer = tObject({
tracing: tChannel(['Tracing']),
});
Expand Down Expand Up @@ -367,7 +368,7 @@ scheme.LocalUtilsGlobToRegexResult = tObject({
});
scheme.RootInitializer = tOptional(tObject({}));
scheme.RootInitializeParams = tObject({
sdkLanguage: tEnum(['javascript', 'python', 'java', 'csharp']),
sdkLanguage: tType('SDKLanguage'),
});
scheme.RootInitializeResult = tObject({
playwright: tChannel(['Playwright']),
Expand Down Expand Up @@ -456,7 +457,7 @@ scheme.DebugControllerPausedEvent = tObject({
});
scheme.DebugControllerInitializeParams = tObject({
codegenId: tString,
sdkLanguage: tEnum(['javascript', 'python', 'java', 'csharp']),
sdkLanguage: tType('SDKLanguage'),
});
scheme.DebugControllerInitializeResult = tOptional(tObject({}));
scheme.DebugControllerSetReportStateChangedParams = tObject({
Expand Down Expand Up @@ -1659,11 +1660,11 @@ scheme.FrameFrameElementParams = tOptional(tObject({}));
scheme.FrameFrameElementResult = tObject({
element: tChannel(['ElementHandle']),
});
scheme.FrameGenerateLocatorStringParams = tObject({
scheme.FrameResolveSelectorParams = tObject({
selector: tString,
});
scheme.FrameGenerateLocatorStringResult = tObject({
value: tOptional(tString),
scheme.FrameResolveSelectorResult = tObject({
resolvedSelector: tString,
});
scheme.FrameHighlightParams = tObject({
selector: tString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Br
return { value: await this._frame.innerHTML(progress, params.selector, params) };
}

async generateLocatorString(params: channels.FrameGenerateLocatorStringParams, progress: Progress): Promise<channels.FrameGenerateLocatorStringResult> {
return { value: await this._frame.generateLocatorString(progress, params.selector) };
async resolveSelector(params: channels.FrameResolveSelectorParams, progress: Progress): Promise<channels.FrameResolveSelectorResult> {
return await this._frame.resolveSelector(progress, params.selector);
}

async getAttribute(params: channels.FrameGetAttributeParams, progress: Progress): Promise<channels.FrameGetAttributeResult> {
Expand Down
11 changes: 6 additions & 5 deletions packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1179,16 +1179,16 @@ export class Frame extends SdkObject {
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true /* performActionPreChecks */, handle => handle._blur(progress)));
}

async generateLocatorString(progress: Progress, selector: string): Promise<string | undefined> {
async resolveSelector(progress: Progress, selector: string): Promise<{ resolvedSelector: string }> {
const element = await progress.race(this.selectors.query(selector));
if (!element)
throw new Error(`No element matching ${this._asLocator(selector)}`);
throw new Error(`No element matching ${selector}`);

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

let frame: Frame | null = element._frame;
const result = [generated];
Expand All @@ -1200,12 +1200,13 @@ export class Frame extends SdkObject {
}, {}));
frameElement.dispose();
if (generated === 'error:notconnected' || !generated)
throw new Error(`Unable to generate locator for ${this._asLocator(selector)}`);
throw new Error(`Unable to generate locator for ${selector}`);
result.push(generated);
}
frame = frame.parentFrame();
}
return asLocator(this._page.browserContext._browser.sdkLanguage(), result.reverse().join(' >> internal:control=enter-frame >> '));
const resolvedSelector = result.reverse().join(' >> internal:control=enter-frame >> ');
return { resolvedSelector };
}

async textContent(progress: Progress, selector: string, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise<string | null> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export const methodMetainfo = new Map<string, { internal?: boolean, title?: stri
['Frame.fill', { title: 'Fill "{value}"', slowMo: true, snapshot: true, pausesBeforeInput: true, }],
['Frame.focus', { title: 'Focus', slowMo: true, snapshot: true, }],
['Frame.frameElement', { internal: true, }],
['Frame.generateLocatorString', { internal: true, }],
['Frame.resolveSelector', { internal: true, }],
['Frame.highlight', { internal: true, }],
['Frame.getAttribute', { internal: true, snapshot: true, }],
['Frame.goto', { title: 'Navigate to "{url}"', slowMo: true, snapshot: true, }],
Expand Down
15 changes: 8 additions & 7 deletions packages/protocol/src/channels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ export type FormField = {
},
};

export type SDKLanguage = 'javascript' | 'python' | 'java' | 'csharp';
// ----------- APIRequestContext -----------
export type APIRequestContextInitializer = {
tracing: TracingChannel,
Expand Down Expand Up @@ -608,7 +609,7 @@ export interface RootChannel extends RootEventTarget, Channel {
initialize(params: RootInitializeParams, progress?: Progress): Promise<RootInitializeResult>;
}
export type RootInitializeParams = {
sdkLanguage: 'javascript' | 'python' | 'java' | 'csharp',
sdkLanguage: SDKLanguage,
};
export type RootInitializeOptions = {

Expand Down Expand Up @@ -769,7 +770,7 @@ export type DebugControllerPausedEvent = {
};
export type DebugControllerInitializeParams = {
codegenId: string,
sdkLanguage: 'javascript' | 'python' | 'java' | 'csharp',
sdkLanguage: SDKLanguage,
};
export type DebugControllerInitializeOptions = {

Expand Down Expand Up @@ -2649,7 +2650,7 @@ export interface FrameChannel extends FrameEventTarget, Channel {
fill(params: FrameFillParams, progress?: Progress): Promise<FrameFillResult>;
focus(params: FrameFocusParams, progress?: Progress): Promise<FrameFocusResult>;
frameElement(params?: FrameFrameElementParams, progress?: Progress): Promise<FrameFrameElementResult>;
generateLocatorString(params: FrameGenerateLocatorStringParams, progress?: Progress): Promise<FrameGenerateLocatorStringResult>;
resolveSelector(params: FrameResolveSelectorParams, progress?: Progress): Promise<FrameResolveSelectorResult>;
highlight(params: FrameHighlightParams, progress?: Progress): Promise<FrameHighlightResult>;
getAttribute(params: FrameGetAttributeParams, progress?: Progress): Promise<FrameGetAttributeResult>;
goto(params: FrameGotoParams, progress?: Progress): Promise<FrameGotoResult>;
Expand Down Expand Up @@ -2905,14 +2906,14 @@ export type FrameFrameElementOptions = {};
export type FrameFrameElementResult = {
element: ElementHandleChannel,
};
export type FrameGenerateLocatorStringParams = {
export type FrameResolveSelectorParams = {
selector: string,
};
export type FrameGenerateLocatorStringOptions = {
export type FrameResolveSelectorOptions = {

};
export type FrameGenerateLocatorStringResult = {
value?: string,
export type FrameResolveSelectorResult = {
resolvedSelector: string,
};
export type FrameHighlightParams = {
selector: string,
Expand Down
28 changes: 12 additions & 16 deletions packages/protocol/src/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ FormField:
mimeType: string?
buffer: binary

SDKLanguage:
type: enum
literals:
- javascript
- python
- java
- csharp

APIRequestContext:
type: interface

Expand Down Expand Up @@ -774,13 +782,7 @@ Root:
initialize:
internal: true
parameters:
sdkLanguage:
type: enum
literals:
- javascript
- python
- java
- csharp
sdkLanguage: SDKLanguage
returns:
playwright: Playwright

Expand Down Expand Up @@ -883,13 +885,7 @@ DebugController:
internal: true
parameters:
codegenId: string
sdkLanguage:
type: enum
literals:
- javascript
- python
- java
- csharp
sdkLanguage: SDKLanguage

setReportStateChanged:
internal: true
Expand Down Expand Up @@ -2307,12 +2303,12 @@ Frame:
returns:
element: ElementHandle

generateLocatorString:
resolveSelector:
internal: true
parameters:
selector: string
returns:
value: string?
resolvedSelector: string

highlight:
internal: true
Expand Down
22 changes: 14 additions & 8 deletions tests/page/page-aria-snapshot-ai.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
* limitations under the License.
*/

// @ts-ignore
import { asLocator } from 'playwright-core/lib/utils';

import { test as it, expect, unshift } from './pageTest';

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

{
const locator = await (page.locator('aria-ref=e1') as any)._generateLocatorString();
expect(locator).toBe(`locator('body')`);
const { resolvedSelector } = await (page.locator('aria-ref=e1') as any)._resolveSelector();
const sourceCode = asLocator('javascript', resolvedSelector);
expect(sourceCode).toBe(`locator('body')`);
}
{
const locator = await (page.locator('aria-ref=f3e2') as any)._generateLocatorString();
expect(locator).toBe(`locator('iframe[name="2frames"]').contentFrame().locator('iframe[name="dos"]').contentFrame().getByText('Hi, I\\'m frame')`);
const { resolvedSelector } = await (page.locator('aria-ref=f3e2') as any)._resolveSelector();
const sourceCode = asLocator('javascript', resolvedSelector);
expect(sourceCode).toBe(`locator('iframe[name="2frames"]').contentFrame().locator('iframe[name="dos"]').contentFrame().getByText('Hi, I\\'m frame')`);
}
{
// Should tolerate .describe().
const locator = await (page.locator('aria-ref=f2e2').describe('foo bar') as any)._generateLocatorString();
expect(locator).toBe(`locator('iframe[name=\"2frames\"]').contentFrame().locator('iframe[name=\"uno\"]').contentFrame().getByText('Hi, I\\'m frame')`);
const { resolvedSelector } = await (page.locator('aria-ref=f2e2').describe('foo bar') as any)._resolveSelector();
const sourceCode = asLocator('javascript', resolvedSelector);
expect(sourceCode).toBe(`locator('iframe[name=\"2frames\"]').contentFrame().locator('iframe[name=\"uno\"]').contentFrame().getByText('Hi, I\\'m frame')`);
}
{
const error = await (page.locator('aria-ref=e1000') as any)._generateLocatorString().catch(e => e);
expect(error.message).toContain(`No element matching locator('aria-ref=e1000')`);
const error = await (page.locator('aria-ref=e1000') as any)._resolveSelector().catch(e => e);
expect(error.message).toContain(`No element matching aria-ref=e1000`);
}
});

Expand Down
Loading