fix(locators): escape >> inside a regular expression (#23631)

To avoid selector being parsed as a chain.

Fixes #23540.
This commit is contained in:
Dmitry Gozman 2023-06-12 10:34:37 -07:00 committed by GitHub
parent dd9a49690b
commit dd417d83d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 36 additions and 18 deletions

View File

@ -29,14 +29,10 @@ export type ByRoleOptions = {
};
function getByAttributeTextSelector(attrName: string, text: string | RegExp, options?: { exact?: boolean }): string {
if (!isString(text))
return `internal:attr=[${attrName}=${text}]`;
return `internal:attr=[${attrName}=${escapeForAttributeSelector(text, options?.exact || false)}]`;
}
export function getByTestIdSelector(testIdAttributeName: string, testId: string | RegExp): string {
if (!isString(testId))
return `internal:testid=[${testIdAttributeName}=${testId}]`;
return `internal:testid=[${testIdAttributeName}=${escapeForAttributeSelector(testId, true)}]`;
}

View File

@ -69,11 +69,13 @@ export function normalizeWhiteSpace(text: string): string {
export function escapeForTextSelector(text: string | RegExp, exact: boolean): string {
if (typeof text !== 'string')
return String(text);
return String(text).replace(/>>/g, '\\>\\>');
return `${JSON.stringify(text)}${exact ? 's' : 'i'}`;
}
export function escapeForAttributeSelector(value: string, exact: boolean): string {
export function escapeForAttributeSelector(value: string | RegExp, exact: boolean): string {
if (typeof value !== 'string')
return String(value).replace(/>>/g, '\\>\\>');
// TODO: this should actually be
// cssEscape(value).replace(/\\ /g, ' ')
// However, our attribute selectors do not conform to CSS parsing spec,

View File

@ -177,12 +177,12 @@ wo"rld</label><input id=control />`);
input.setAttribute('title', 'hello my\nwo"rld');
input.setAttribute('alt', 'hello my\nwo"rld');
});
await expect(page.getByText('hello my\nwo"rld')).toHaveAttribute('id', 'label');
await expect(page.getByText('hello my wo"rld')).toHaveAttribute('id', 'label');
await expect(page.getByLabel('hello my\nwo"rld')).toHaveAttribute('id', 'control');
await expect(page.getByPlaceholder('hello my\nwo"rld')).toHaveAttribute('id', 'control');
await expect(page.getByAltText('hello my\nwo"rld')).toHaveAttribute('id', 'control');
await expect(page.getByTitle('hello my\nwo"rld')).toHaveAttribute('id', 'control');
await expect.soft(page.getByText('hello my\nwo"rld')).toHaveAttribute('id', 'label');
await expect.soft(page.getByText('hello my wo"rld')).toHaveAttribute('id', 'label');
await expect.soft(page.getByLabel('hello my\nwo"rld')).toHaveAttribute('id', 'control');
await expect.soft(page.getByPlaceholder('hello my\nwo"rld')).toHaveAttribute('id', 'control');
await expect.soft(page.getByAltText('hello my\nwo"rld')).toHaveAttribute('id', 'control');
await expect.soft(page.getByTitle('hello my\nwo"rld')).toHaveAttribute('id', 'control');
await page.setContent(`<label id=label for=control>Hello my
world</label><input id=control />`);
@ -191,12 +191,12 @@ world</label><input id=control />`);
input.setAttribute('title', 'hello my\nworld');
input.setAttribute('alt', 'hello my\nworld');
});
await expect(page.getByText('hello my\nworld')).toHaveAttribute('id', 'label');
await expect(page.getByText('hello my world')).toHaveAttribute('id', 'label');
await expect(page.getByLabel('hello my\nworld')).toHaveAttribute('id', 'control');
await expect(page.getByPlaceholder('hello my\nworld')).toHaveAttribute('id', 'control');
await expect(page.getByAltText('hello my\nworld')).toHaveAttribute('id', 'control');
await expect(page.getByTitle('hello my\nworld')).toHaveAttribute('id', 'control');
await expect.soft(page.getByText('hello my\nworld')).toHaveAttribute('id', 'label');
await expect.soft(page.getByText('hello my world')).toHaveAttribute('id', 'label');
await expect.soft(page.getByLabel('hello my\nworld')).toHaveAttribute('id', 'control');
await expect.soft(page.getByPlaceholder('hello my\nworld')).toHaveAttribute('id', 'control');
await expect.soft(page.getByAltText('hello my\nworld')).toHaveAttribute('id', 'control');
await expect.soft(page.getByTitle('hello my\nworld')).toHaveAttribute('id', 'control');
await page.setContent(`<div id=target title="my title">Text here</div>`);
await expect.soft(page.getByTitle('my title', { exact: true })).toHaveCount(1, { timeout: 500 });
@ -204,6 +204,25 @@ world</label><input id=control />`);
await expect.soft(page.getByTitle('my t\\itle', { exact: true })).toHaveCount(0, { timeout: 500 });
await expect.soft(page.getByTitle('my t\\\itle', { exact: true })).toHaveCount(0, { timeout: 500 });
await expect.soft(page.getByTitle('my t\\\\itle', { exact: true })).toHaveCount(0, { timeout: 500 });
await page.setContent(`<label for=target>foo &gt;&gt; bar</label><input id=target>`);
await page.$eval('input', input => {
input.setAttribute('placeholder', 'foo >> bar');
input.setAttribute('title', 'foo >> bar');
input.setAttribute('alt', 'foo >> bar');
});
expect.soft(await page.getByText('foo >> bar').textContent()).toBe('foo >> bar');
await expect.soft(page.locator('label')).toHaveText('foo >> bar');
await expect.soft(page.getByText('foo >> bar')).toHaveText('foo >> bar');
expect.soft(await page.getByText(/foo >> bar/).textContent()).toBe('foo >> bar');
await expect.soft(page.getByLabel('foo >> bar')).toHaveAttribute('id', 'target');
await expect.soft(page.getByLabel(/foo >> bar/)).toHaveAttribute('id', 'target');
await expect.soft(page.getByPlaceholder('foo >> bar')).toHaveAttribute('id', 'target');
await expect.soft(page.getByAltText('foo >> bar')).toHaveAttribute('id', 'target');
await expect.soft(page.getByTitle('foo >> bar')).toHaveAttribute('id', 'target');
await expect.soft(page.getByPlaceholder(/foo >> bar/)).toHaveAttribute('id', 'target');
await expect.soft(page.getByAltText(/foo >> bar/)).toHaveAttribute('id', 'target');
await expect.soft(page.getByTitle(/foo >> bar/)).toHaveAttribute('id', 'target');
});
it('getByRole escaping', async ({ page }) => {

View File

@ -76,6 +76,7 @@ it('should work @smoke', async ({ page }) => {
await page.setContent(`<div>Hi&gt;&gt;<span></span></div>`);
expect(await page.$eval(`text="Hi>>">>span`, e => e.outerHTML)).toBe(`<span></span>`);
expect(await page.$eval(`text=/Hi\\>\\>/ >> span`, e => e.outerHTML)).toBe(`<span></span>`);
await page.setContent(`<div>a<br>b</div><div>a</div>`);
expect(await page.$eval(`text=a`, e => e.outerHTML)).toBe('<div>a<br>b</div>');