Skip to content

Commit

Permalink
fix(locators): escape >> inside a regular expression (#23631)
Browse files Browse the repository at this point in the history
To avoid selector being parsed as a chain.

Fixes #23540.
  • Loading branch information
dgozman committed Jun 12, 2023
1 parent dd9a496 commit dd417d8
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 18 deletions.
4 changes: 0 additions & 4 deletions packages/playwright-core/src/utils/isomorphic/locatorUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}]`;
}

Expand Down
6 changes: 4 additions & 2 deletions packages/playwright-core/src/utils/isomorphic/stringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
43 changes: 31 additions & 12 deletions tests/page/selectors-get-by.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 />`);
Expand All @@ -191,19 +191,38 @@ 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 });
await expect.soft(page.getByTitle('my t\itle', { exact: true })).toHaveCount(1, { 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 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 }) => {
Expand Down
1 change: 1 addition & 0 deletions tests/page/selectors-text.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>');
Expand Down

0 comments on commit dd417d8

Please sign in to comment.