diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index 4a48d4dbf1..8dfef202b1 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -908,6 +908,16 @@ Attribute name to get the value for. * since: v1.8 +## method: Frame.getByAltText +* since: v1.27 +- returns: <[Locator]> + +%%-template-locator-get-by-alt-text-%% + +### param: Frame.getByAltText.text = %%-locator-get-by-text-text-%% +### option: Frame.getByAltText.exact = %%-locator-get-by-text-exact-%% + + ## method: Frame.getByLabelText * since: v1.27 - returns: <[Locator]> @@ -960,6 +970,16 @@ Attribute name to get the value for. ### option: Frame.getByText.exact = %%-locator-get-by-text-exact-%% +## method: Frame.getByTitle +* since: v1.27 +- returns: <[Locator]> + +%%-template-locator-get-by-title-%% + +### param: Frame.getByTitle.text = %%-locator-get-by-text-text-%% +### option: Frame.getByTitle.exact = %%-locator-get-by-text-exact-%% + + ## async method: Frame.goto * since: v1.8 * langs: diff --git a/docs/src/api/class-framelocator.md b/docs/src/api/class-framelocator.md index 78b02dc374..019354cf33 100644 --- a/docs/src/api/class-framelocator.md +++ b/docs/src/api/class-framelocator.md @@ -114,6 +114,16 @@ in that iframe. * since: v1.17 +## method: FrameLocator.getByAltText +* since: v1.27 +- returns: <[Locator]> + +%%-template-locator-get-by-alt-text-%% + +### param: FrameLocator.getByAltText.text = %%-locator-get-by-text-text-%% +### option: FrameLocator.getByAltText.exact = %%-locator-get-by-text-exact-%% + + ## method: FrameLocator.getByLabelText * since: v1.27 - returns: <[Locator]> @@ -165,6 +175,16 @@ in that iframe. ### option: FrameLocator.getByText.exact = %%-locator-get-by-text-exact-%% +## method: FrameLocator.getByTitle +* since: v1.27 +- returns: <[Locator]> + +%%-template-locator-get-by-title-%% + +### param: FrameLocator.getByTitle.text = %%-locator-get-by-text-text-%% +### option: FrameLocator.getByTitle.exact = %%-locator-get-by-text-exact-%% + + ## method: FrameLocator.last * since: v1.17 - returns: <[FrameLocator]> diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index 7f3465bfeb..1f2b0a3df7 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -634,6 +634,16 @@ Attribute name to get the value for. * since: v1.14 +## method: Locator.getByAltText +* since: v1.27 +- returns: <[Locator]> + +%%-template-locator-get-by-alt-text-%% + +### param: Locator.getByAltText.text = %%-locator-get-by-text-text-%% +### option: Locator.getByAltText.exact = %%-locator-get-by-text-exact-%% + + ## method: Locator.getByLabelText * since: v1.27 - returns: <[Locator]> @@ -685,6 +695,16 @@ Attribute name to get the value for. ### option: Locator.getByText.exact = %%-locator-get-by-text-exact-%% +## method: Locator.getByTitle +* since: v1.27 +- returns: <[Locator]> + +%%-template-locator-get-by-title-%% + +### param: Locator.getByTitle.text = %%-locator-get-by-text-text-%% +### option: Locator.getByTitle.exact = %%-locator-get-by-text-exact-%% + + ## async method: Locator.highlight * since: v1.20 diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 63f1083951..e4bf776050 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -2183,6 +2183,16 @@ Attribute name to get the value for. * since: v1.8 +## method: Page.getByAltText +* since: v1.27 +- returns: <[Locator]> + +%%-template-locator-get-by-alt-text-%% + +### param: Page.getByAltText.text = %%-locator-get-by-text-text-%% +### option: Page.getByAltText.exact = %%-locator-get-by-text-exact-%% + + ## method: Page.getByLabelText * since: v1.27 - returns: <[Locator]> @@ -2234,6 +2244,16 @@ Attribute name to get the value for. ### option: Page.getByText.exact = %%-locator-get-by-text-exact-%% +## method: Page.getByTitle +* since: v1.27 +- returns: <[Locator]> + +%%-template-locator-get-by-title-%% + +### param: Page.getByTitle.text = %%-locator-get-by-text-text-%% +### option: Page.getByTitle.exact = %%-locator-get-by-text-exact-%% + + ## async method: Page.goBack * since: v1.8 - returns: <[null]|[Response]> diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 669aa40d0d..109bd5781d 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1180,6 +1180,14 @@ Locate element by the test id. By default, the `data-testid` attribute is used a Allows locating elements that contain given text. +## template-locator-get-by-alt-text + +Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle": + +```html +Castle +``` + ## template-locator-get-by-label-text Allows locating input elements by the text of the associated label. For example, this method will find the input by label text Password in the following DOM: @@ -1188,6 +1196,7 @@ Allows locating input elements by the text of the associated label. For example, ``` + ## template-locator-get-by-placeholder-text Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder "Country": @@ -1202,3 +1211,10 @@ Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2 Note that many html elements have an implicitly [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values. +## template-locator-get-by-title + +Allows locating elements by their title. For example, this method will find the button by its title "Submit": + +```html + +``` diff --git a/packages/playwright-core/src/client/frame.ts b/packages/playwright-core/src/client/frame.ts index a3420ee834..9a40225f12 100644 --- a/packages/playwright-core/src/client/frame.ts +++ b/packages/playwright-core/src/client/frame.ts @@ -303,6 +303,10 @@ export class Frame extends ChannelOwner implements api.Fr return this.locator(Locator.getByTestIdSelector(testId)); } + getByAltText(text: string | RegExp, options?: { exact?: boolean }): Locator { + return this.locator(Locator.getByAltTextSelector(text, options)); + } + getByLabelText(text: string | RegExp, options?: { exact?: boolean }): Locator { return this.locator(Locator.getByLabelTextSelector(text, options)); } @@ -315,6 +319,10 @@ export class Frame extends ChannelOwner implements api.Fr return this.locator(Locator.getByTextSelector(text, options)); } + getByTitle(text: string | RegExp, options?: { exact?: boolean }): Locator { + return this.locator(Locator.getByTitleSelector(text, options)); + } + getByRole(role: string, options: ByRoleOptions = {}): Locator { return this.locator(Locator.getByRoleSelector(role, options)); } diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index 90cd069684..6d4386b93b 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -52,7 +52,13 @@ export class Locator implements api.Locator { } static getByTestIdSelector(testId: string): string { - return `attr=[${Locator._testIdAttributeName}=${JSON.stringify(testId)}]`; + return Locator.getByAttributeTextSelector(this._testIdAttributeName, testId, { exact: true }); + } + + private static getByAttributeTextSelector(attrName: string, text: string | RegExp, options?: { exact?: boolean }): string { + if (!isString(text)) + return `attr=[${attrName}=${text}]`; + return `attr=[${attrName}=${JSON.stringify(text)}${options?.exact ? 's' : 'i'}]`; } static getByLabelTextSelector(text: string | RegExp, options?: { exact?: boolean }): string { @@ -63,10 +69,16 @@ export class Locator implements api.Locator { return selector + ' >> control=resolve-label'; } + static getByAltTextSelector(text: string | RegExp, options?: { exact?: boolean }): string { + return Locator.getByAttributeTextSelector('alt', text, options); + } + + static getByTitleSelector(text: string | RegExp, options?: { exact?: boolean }): string { + return Locator.getByAttributeTextSelector('title', text, options); + } + static getByPlaceholderTextSelector(text: string | RegExp, options?: { exact?: boolean }): string { - if (!isString(text)) - return `attr=[placeholder=${text}]`; - return `attr=[placeholder=${JSON.stringify(text)}${options?.exact ? 's' : 'i'}]`; + return Locator.getByAttributeTextSelector('placeholder', text, options); } static getByTextSelector(text: string | RegExp, options?: { exact?: boolean }): string { @@ -199,6 +211,10 @@ export class Locator implements api.Locator { return this.locator(Locator.getByTestIdSelector(testId)); } + getByAltText(text: string | RegExp, options?: { exact?: boolean }): Locator { + return this.locator(Locator.getByAltTextSelector(text, options)); + } + getByLabelText(text: string | RegExp, options?: { exact?: boolean }): Locator { return this.locator(Locator.getByLabelTextSelector(text, options)); } @@ -211,6 +227,10 @@ export class Locator implements api.Locator { return this.locator(Locator.getByTextSelector(text, options)); } + getByTitle(text: string | RegExp, options?: { exact?: boolean }): Locator { + return this.locator(Locator.getByTitleSelector(text, options)); + } + getByRole(role: string, options: ByRoleOptions = {}): Locator { return this.locator(Locator.getByRoleSelector(role, options)); } @@ -393,6 +413,10 @@ export class FrameLocator implements api.FrameLocator { return this.locator(Locator.getByTestIdSelector(testId)); } + getByAltText(text: string | RegExp, options?: { exact?: boolean }): Locator { + return this.locator(Locator.getByAltTextSelector(text, options)); + } + getByLabelText(text: string | RegExp, options?: { exact?: boolean }): Locator { return this.locator(Locator.getByLabelTextSelector(text, options)); } @@ -405,6 +429,10 @@ export class FrameLocator implements api.FrameLocator { return this.locator(Locator.getByTextSelector(text, options)); } + getByTitle(text: string | RegExp, options?: { exact?: boolean }): Locator { + return this.locator(Locator.getByTitleSelector(text, options)); + } + getByRole(role: string, options: ByRoleOptions = {}): Locator { return this.locator(Locator.getByRoleSelector(role, options)); } diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index b28d87d216..42cfac43b1 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -568,6 +568,10 @@ export class Page extends ChannelOwner implements api.Page return this.mainFrame().getByTestId(testId); } + getByAltText(text: string | RegExp, options?: { exact?: boolean }): Locator { + return this.mainFrame().getByAltText(text, options); + } + getByLabelText(text: string | RegExp, options?: { exact?: boolean }): Locator { return this.mainFrame().getByLabelText(text, options); } @@ -580,6 +584,10 @@ export class Page extends ChannelOwner implements api.Page return this.mainFrame().getByText(text, options); } + getByTitle(text: string | RegExp, options?: { exact?: boolean }): Locator { + return this.mainFrame().getByTitle(text, options); + } + getByRole(role: string, options: ByRoleOptions = {}): Locator { return this.mainFrame().getByRole(role, options); } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index e5d4a52324..db80053da6 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -2456,6 +2456,23 @@ export interface Page { timeout?: number; }): Promise; + /** + * Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle": + * + * ```html + * Castle + * ``` + * + * @param text Text to locate the element for. + * @param options + */ + getByAltText(text: string|RegExp, options?: { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + exact?: boolean; + }): Locator; + /** * Allows locating input elements by the text of the associated label. For example, this method will find the input by * label text Password in the following DOM: @@ -2588,6 +2605,23 @@ export interface Page { exact?: boolean; }): Locator; + /** + * Allows locating elements by their title. For example, this method will find the button by its title "Submit": + * + * ```html + * + * ``` + * + * @param text Text to locate the element for. + * @param options + */ + getByTitle(text: string|RegExp, options?: { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + exact?: boolean; + }): Locator; + /** * Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the * last redirect. If can not go back, returns `null`. @@ -5508,6 +5542,23 @@ export interface Frame { timeout?: number; }): Promise; + /** + * Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle": + * + * ```html + * Castle + * ``` + * + * @param text Text to locate the element for. + * @param options + */ + getByAltText(text: string|RegExp, options?: { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + exact?: boolean; + }): Locator; + /** * Allows locating input elements by the text of the associated label. For example, this method will find the input by * label text Password in the following DOM: @@ -5640,6 +5691,23 @@ export interface Frame { exact?: boolean; }): Locator; + /** + * Allows locating elements by their title. For example, this method will find the button by its title "Submit": + * + * ```html + * + * ``` + * + * @param text Text to locate the element for. + * @param options + */ + getByTitle(text: string|RegExp, options?: { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + exact?: boolean; + }): Locator; + /** * Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the * last redirect. @@ -9908,6 +9976,23 @@ export interface Locator { timeout?: number; }): Promise; + /** + * Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle": + * + * ```html + * Castle + * ``` + * + * @param text Text to locate the element for. + * @param options + */ + getByAltText(text: string|RegExp, options?: { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + exact?: boolean; + }): Locator; + /** * Allows locating input elements by the text of the associated label. For example, this method will find the input by * label text Password in the following DOM: @@ -10040,6 +10125,23 @@ export interface Locator { exact?: boolean; }): Locator; + /** + * Allows locating elements by their title. For example, this method will find the button by its title "Submit": + * + * ```html + * + * ``` + * + * @param text Text to locate the element for. + * @param options + */ + getByTitle(text: string|RegExp, options?: { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + exact?: boolean; + }): Locator; + /** * Highlight the corresponding element(s) on the screen. Useful for debugging, don't commit the code that uses * [locator.highlight()](https://playwright.dev/docs/api/class-locator#locator-highlight). @@ -15129,6 +15231,23 @@ export interface FrameLocator { */ frameLocator(selector: string): FrameLocator; + /** + * Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle": + * + * ```html + * Castle + * ``` + * + * @param text Text to locate the element for. + * @param options + */ + getByAltText(text: string|RegExp, options?: { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + exact?: boolean; + }): Locator; + /** * Allows locating input elements by the text of the associated label. For example, this method will find the input by * label text Password in the following DOM: @@ -15261,6 +15380,23 @@ export interface FrameLocator { exact?: boolean; }): Locator; + /** + * Allows locating elements by their title. For example, this method will find the button by its title "Submit": + * + * ```html + * + * ``` + * + * @param text Text to locate the element for. + * @param options + */ + getByTitle(text: string|RegExp, options?: { + /** + * Whether to find an exact match: case-sensitive and whole-string. Default to false. + */ + exact?: boolean; + }): Locator; + /** * Returns locator to the last matching frame. */ diff --git a/tests/page/locator-frame.spec.ts b/tests/page/locator-frame.spec.ts index 6e43190ffb..4755141021 100644 --- a/tests/page/locator-frame.spec.ts +++ b/tests/page/locator-frame.spec.ts @@ -35,7 +35,7 @@ async function routeIframe(page: Page) { 1 2 - + `, contentType: 'text/html' }).catch(() => {}); @@ -252,4 +252,8 @@ it('getBy coverage', async ({ page, server }) => { await expect(input1).toHaveValue(''); const input2 = page.frameLocator('iframe').getByPlaceholderText('Placeholder'); await expect(input2).toHaveValue(''); + const input3 = page.frameLocator('iframe').getByAltText('Alternative'); + await expect(input3).toHaveValue(''); + const input4 = page.frameLocator('iframe').getByTitle('Title'); + await expect(input4).toHaveValue(''); }); diff --git a/tests/page/selectors-get-by.spec.ts b/tests/page/selectors-get-by.spec.ts index d68ae6f2a1..7c04ee4b20 100644 --- a/tests/page/selectors-get-by.spec.ts +++ b/tests/page/selectors-get-by.spec.ts @@ -58,3 +58,31 @@ it('getByPlaceholderText should work', async ({ page }) => { await expect(page.mainFrame().getByPlaceholderText('hello')).toHaveCount(2); await expect(page.locator('div').getByPlaceholderText('hello')).toHaveCount(2); }); + +it('getByAltText should work', async ({ page }) => { + await page.setContent(`
+ + +
`); + await expect(page.getByAltText('hello')).toHaveCount(2); + await expect(page.getByAltText('Hello', { exact: true })).toHaveCount(1); + await expect(page.getByAltText(/wor/i)).toHaveCount(1); + + // Coverage + await expect(page.mainFrame().getByAltText('hello')).toHaveCount(2); + await expect(page.locator('div').getByAltText('hello')).toHaveCount(2); +}); + +it('getByTitle should work', async ({ page }) => { + await page.setContent(`
+ + +
`); + await expect(page.getByTitle('hello')).toHaveCount(2); + await expect(page.getByTitle('Hello', { exact: true })).toHaveCount(1); + await expect(page.getByTitle(/wor/i)).toHaveCount(1); + + // Coverage + await expect(page.mainFrame().getByTitle('hello')).toHaveCount(2); + await expect(page.locator('div').getByTitle('hello')).toHaveCount(2); +});