fix(text selector): ignore non-leading quote when parsing (#13170)

Previously, any unpaired quote in the text selector "escaped"
everything till the end of the selector string, and so any
subsequent chained selectors, including ">>" separator were ignored.

An example of misbehaving selector: `text=19" >> nth=1`.

Now, when text selector contains a non-leading quote, selector parser
does not assume it should escape ">>" separator and correctly
tokenizes all selectors from the chain.

Note that this behavior is a workaround for the fact that our
text selectors is somewhat poorly defined in this area. That said,
this workaround seems to be safe enough. It still does not work for
unpaired leading quotes like this: `text="19 >> nth=1`.
This commit is contained in:
Dmitry Gozman 2022-03-30 09:33:32 -07:00 committed by GitHub
parent 705a994f18
commit a8d4a8aa52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 1 deletions

View File

@ -173,6 +173,13 @@ function parseSelectorString(selector: string): ParsedSelectorStrings {
return result;
}
const shouldIgnoreTextSelectorQuote = () => {
const prefix = selector.substring(start, index);
const match = prefix.match(/^\s*text\s*=(.*)$/);
// Must be a text selector with some text before the quote.
return !!match && !!match[1];
};
while (index < selector.length) {
const c = selector[index];
if (c === '\\' && index + 1 < selector.length) {
@ -180,7 +187,7 @@ function parseSelectorString(selector: string): ParsedSelectorStrings {
} else if (c === quote) {
quote = undefined;
index++;
} else if (!quote && (c === '"' || c === '\'' || c === '`')) {
} else if (!quote && (c === '"' || c === '\'' || c === '`') && !shouldIgnoreTextSelectorQuote()) {
quote = c;
index++;
} else if (!quote && c === '>' && selector[index + 1] === '>') {

View File

@ -303,6 +303,7 @@ it('should be case sensitive if quotes are specified', async ({ page }) => {
await page.setContent(`<div>yo</div><div>ya</div><div>\nye </div>`);
expect(await page.$eval(`text=yA`, e => e.outerHTML)).toBe('<div>ya</div>');
expect(await page.$(`text="yA"`)).toBe(null);
expect(await page.$(`text= "ya"`)).toBe(null);
});
it('should search for a substring without quotes', async ({ page }) => {
@ -411,3 +412,30 @@ it('should work with leading and trailing spaces', async ({ page }) => {
await expect(page.locator('text=Add widget')).toBeVisible();
await expect(page.locator('text= Add widget ')).toBeVisible();
});
it('should work with unpaired quotes when not at the start', async ({ page }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/12719' });
await page.setContent(`
<div>hello"world<span>yay</span></div>
<div>hello'world<span>nay</span></div>
<div>hello\`world<span>oh</span></div>
<div>hello\`world<span>oh2</span></div>
`);
expect(await page.$eval('text=lo" >> span', e => e.outerHTML)).toBe('<span>yay</span>');
expect(await page.$eval(' text=lo" >> span', e => e.outerHTML)).toBe('<span>yay</span>');
expect(await page.$eval('text =lo" >> span', e => e.outerHTML)).toBe('<span>yay</span>');
expect(await page.$eval('text= lo" >> span', e => e.outerHTML)).toBe('<span>yay</span>');
expect(await page.$eval(' text = lo" >> span', e => e.outerHTML)).toBe('<span>yay</span>');
expect(await page.$eval('text=o"wor >> span', e => e.outerHTML)).toBe('<span>yay</span>');
expect(await page.$eval(`text=lo'wor >> span`, e => e.outerHTML)).toBe('<span>nay</span>');
expect(await page.$eval(`text=o' >> span`, e => e.outerHTML)).toBe('<span>nay</span>');
expect(await page.$eval(`text=ello\`wor >> span`, e => e.outerHTML)).toBe('<span>oh</span>');
await expect(page.locator(`text=ello\`wor`).locator('span').first()).toHaveText('oh');
await expect(page.locator(`text=ello\`wor`).locator('span').nth(1)).toHaveText('oh2');
expect(await page.$(`text='wor >> span`)).toBe(null);
expect(await page.$(`text=" >> span`)).toBe(null);
expect(await page.$(`text=\` >> span`)).toBe(null);
});