diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index d361b0b1a2f29..a1ba807d90eab 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -1803,6 +1803,13 @@ var banana = await page.GetByRole(AriaRole.Listitem).Last(1); ### option: Locator.locator.hasNotText = %%-locator-option-has-not-text-%% * since: v1.33 +## async method: Locator.normalize +* since: v1.59 +- returns: <[Locator]> + +Returns a new locator that uses best practices for referencing the matched element, prioritizing test ids, +aria roles, and other user-facing attributes over CSS selectors. This is useful for converting implementation-detail selectors into more resilient, human-readable locators. + ## method: Locator.nth * since: v1.14 - returns: <[Locator]> @@ -2562,12 +2569,6 @@ If you need to assert text on the page, prefer [`method: LocatorAssertions.toHav ### option: Locator.textContent.timeout = %%-input-timeout-js-%% * since: v1.14 -## async method: Locator.toCode -* since: v1.59 -- returns: <[string]> - -Returns a code string for a locator that uses best practices for referencing the matched element, prioritizing test ids, aria roles, and other user-facing attributes over CSS selectors. - ## method: Locator.toString * since: v1.57 * langs: js diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 98132f06fd933..12790c2e29830 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -14213,6 +14213,13 @@ export interface Locator { hasText?: string|RegExp; }): Locator; + /** + * Returns a new locator that uses best practices for referencing the matched element, prioritizing test ids, aria + * roles, and other user-facing attributes over CSS selectors. This is useful for converting implementation-detail + * selectors into more resilient, human-readable locators. + */ + normalize(): Promise; + /** * Returns locator to the n-th matching element. It's zero based, `nth(0)` selects the first element. * @@ -14802,12 +14809,6 @@ export interface Locator { timeout?: number; }): Promise; - /** - * Returns a code string for a locator that uses best practices for referencing the matched element, prioritizing test - * ids, aria roles, and other user-facing attributes over CSS selectors. - */ - toCode(): Promise; - /** * Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the * text. diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index 6e543098eef4a..41a35b33b9359 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -254,9 +254,9 @@ export class Locator implements api.Locator { return await this._frame._queryCount(this._selector, _options); } - async toCode(): Promise { + async normalize(): Promise { const { resolvedSelector } = await this._frame._channel.resolveSelector({ selector: this._selector }); - return new Locator(this._frame, resolvedSelector).toString(); + return new Locator(this._frame, resolvedSelector); } async getAttribute(name: string, options?: TimeoutOptions): Promise { diff --git a/packages/playwright-core/src/tools/backend/tab.ts b/packages/playwright-core/src/tools/backend/tab.ts index 4fb129b9ec979..5bd83957492aa 100644 --- a/packages/playwright-core/src/tools/backend/tab.ts +++ b/packages/playwright-core/src/tools/backend/tab.ts @@ -450,8 +450,8 @@ export class Tab extends EventEmitter { let locator = this.page.locator(`aria-ref=${param.ref}`); if (param.element) locator = locator.describe(param.element); - const resolved = await locator.toCode(); - return { locator, resolved }; + const resolved = await locator.normalize(); + return { locator, resolved: resolved.toString() }; } catch (e) { throw new Error(`Ref ${param.ref} not found in the current page snapshot. Try capturing new snapshot.`); } diff --git a/packages/playwright-core/src/tools/backend/verify.ts b/packages/playwright-core/src/tools/backend/verify.ts index 60b65272a4764..48f012216c9e0 100644 --- a/packages/playwright-core/src/tools/backend/verify.ts +++ b/packages/playwright-core/src/tools/backend/verify.ts @@ -37,7 +37,7 @@ const verifyElement = defineTabTool({ for (const frame of tab.page.frames()) { const locator = frame.getByRole(params.role as Parameters[0], { name: params.accessibleName }); if (await locator.count() > 0) { - const resolved = await locator.toCode(); + const resolved = await locator.normalize(); response.addCode(`await expect(page.${resolved}).toBeVisible();`); response.addTextResult('Done'); return; @@ -63,7 +63,7 @@ const verifyText = defineTabTool({ for (const frame of tab.page.frames()) { const locator = frame.getByText(params.text).filter({ visible: true }); if (await locator.count() > 0) { - const resolved = await locator.toCode(); + const resolved = await locator.normalize(); response.addCode(`await expect(page.${resolved}).toBeVisible();`); response.addTextResult('Done'); return; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 98132f06fd933..12790c2e29830 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -14213,6 +14213,13 @@ export interface Locator { hasText?: string|RegExp; }): Locator; + /** + * Returns a new locator that uses best practices for referencing the matched element, prioritizing test ids, aria + * roles, and other user-facing attributes over CSS selectors. This is useful for converting implementation-detail + * selectors into more resilient, human-readable locators. + */ + normalize(): Promise; + /** * Returns locator to the n-th matching element. It's zero based, `nth(0)` selects the first element. * @@ -14802,12 +14809,6 @@ export interface Locator { timeout?: number; }): Promise; - /** - * Returns a code string for a locator that uses best practices for referencing the matched element, prioritizing test - * ids, aria roles, and other user-facing attributes over CSS selectors. - */ - toCode(): Promise; - /** * Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the * text. diff --git a/tests/page/page-aria-snapshot-ai.spec.ts b/tests/page/page-aria-snapshot-ai.spec.ts index d4cf11f00aa77..cadd01161b4b8 100644 --- a/tests/page/page-aria-snapshot-ai.spec.ts +++ b/tests/page/page-aria-snapshot-ai.spec.ts @@ -93,20 +93,20 @@ it('should stitch all frame snapshots', async ({ page, server }) => { expect(href3).toBe(server.PREFIX + '/frames/frame.html'); { - const resolved = await page.locator('aria-ref=e1').toCode(); - expect(resolved).toBe(`locator('body')`); + const resolved = await page.locator('aria-ref=e1').normalize(); + expect(resolved.toString()).toBe(`locator('body')`); } { - const resolved = await page.locator('aria-ref=f4e2').toCode(); - expect(resolved).toBe(`locator('iframe[name="2frames"]').contentFrame().locator('iframe[name="dos"]').contentFrame().getByText('Hi, I\\'m frame')`); + const resolved = await page.locator('aria-ref=f4e2').normalize(); + expect(resolved.toString()).toBe(`locator('iframe[name="2frames"]').contentFrame().locator('iframe[name="dos"]').contentFrame().getByText('Hi, I\\'m frame')`); } { // Should tolerate .describe(). - const resolved = await page.locator('aria-ref=f3e2').describe('foo bar').toCode(); - expect(resolved).toBe(`locator('iframe[name=\"2frames\"]').contentFrame().locator('iframe[name=\"uno\"]').contentFrame().getByText('Hi, I\\'m frame')`); + const resolved = await page.locator('aria-ref=f3e2').describe('foo bar').normalize(); + expect(resolved.toString()).toBe(`locator('iframe[name=\"2frames\"]').contentFrame().locator('iframe[name=\"uno\"]').contentFrame().getByText('Hi, I\\'m frame')`); } { - const error = await page.locator('aria-ref=e1000').toCode().catch(e => e); + const error = await page.locator('aria-ref=e1000').normalize().catch(e => e); expect(error.message).toContain(`No element matching aria-ref=e1000`); } });