Skip to content

Commit fdbd4fe

Browse files
authored
fix(selectors): fix selector parsing for css attributes and quotes (#2389)
- css attribute selector may contain spaces; - >> escaping inside strings was sometimes incorrect. See added test cases for more details.
1 parent 7981e4e commit fdbd4fe

File tree

3 files changed

+45
-3
lines changed

3 files changed

+45
-3
lines changed

src/injected/cssSelectorEngine.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ function parentElementOrShadowHost(element: Element): Element | undefined {
219219
function split(selector: string): string[][] {
220220
let index = 0;
221221
let quote: string | undefined;
222+
let insideAttr = false;
222223
let start = 0;
223224
let space: 'none' | 'before' | 'after' = 'none';
224225
const result: string[][] = [];
@@ -235,7 +236,7 @@ function split(selector: string): string[][] {
235236
};
236237
while (index < selector.length) {
237238
const c = selector[index];
238-
if (!quote && c === ' ') {
239+
if (!quote && !insideAttr && c === ' ') {
239240
if (space === 'none' || space === 'before')
240241
space = 'before';
241242
index++;
@@ -256,10 +257,16 @@ function split(selector: string): string[][] {
256257
} else if (c === quote) {
257258
quote = undefined;
258259
index++;
259-
} else if (c === '\'' || c === '"') {
260+
} else if (!quote && (c === '\'' || c === '"')) {
260261
quote = c;
261262
index++;
262-
} else if (!quote && c === ',') {
263+
} else if (!quote && c === '[') {
264+
insideAttr = true;
265+
index++;
266+
} else if (!quote && insideAttr && c === ']') {
267+
insideAttr = false;
268+
index++;
269+
} else if (!quote && !insideAttr && c === ',') {
263270
appendToResult();
264271
index++;
265272
start = index;

src/selectors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ export function parseSelector(selector: string): types.ParsedSelector {
219219
} else if (c === quote) {
220220
quote = undefined;
221221
index++;
222+
} else if (!quote && (c === '"' || c === '\'' || c === '`')) {
223+
quote = c;
224+
index++;
222225
} else if (!quote && c === '>' && selector[index + 1] === '>') {
223226
append();
224227
index += 2;

test/queryselector.spec.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,14 @@ describe('text selector', () => {
494494
expect(await page.$eval(`text="`, e => e.outerHTML)).toBe('<div> " </div>');
495495
expect(await page.$eval(`text='`, e => e.outerHTML)).toBe('<div> \' </div>');
496496

497+
await page.setContent(`<div>Hi''&gt;&gt;foo=bar</div>`);
498+
expect(await page.$eval(`text="Hi''>>foo=bar"`, e => e.outerHTML)).toBe(`<div>Hi''&gt;&gt;foo=bar</div>`);
499+
await page.setContent(`<div>Hi'"&gt;&gt;foo=bar</div>`);
500+
expect(await page.$eval(`text="Hi'\\">>foo=bar"`, e => e.outerHTML)).toBe(`<div>Hi'"&gt;&gt;foo=bar</div>`);
501+
502+
await page.setContent(`<div>Hi&gt;&gt;<span></span></div>`);
503+
expect(await page.$eval(`text="Hi>>">>span`, e => e.outerHTML)).toBe(`<span></span>`);
504+
497505
await page.setContent(`<div>a<br>b</div><div>a</div>`);
498506
expect(await page.$eval(`text=a`, e => e.outerHTML)).toBe('<div>a<br>b</div>');
499507
expect(await page.$eval(`text=b`, e => e.outerHTML)).toBe('<div>a<br>b</div>');
@@ -622,6 +630,30 @@ describe('css selector', () => {
622630
expect(await page.$eval(`css=[attr='hello,world!']`, e => e.outerHTML)).toBe('<div attr="hello,world!"></div>');
623631
expect(await page.$eval(`css=div[attr="hello,world!"],span`, e => e.outerHTML)).toBe('<span></span>');
624632
});
633+
634+
it('should work with attribute selectors', async({page}) => {
635+
await page.setContent(`<div attr="hello world" attr2="hello-''>>foo=bar[]" attr3="] span"><span></span></div>`);
636+
await page.evaluate(() => window.div = document.querySelector('div'));
637+
const selectors = [
638+
`[attr="hello world"]`,
639+
`[attr = "hello world"]`,
640+
`[attr ~= world]`,
641+
`[attr ^=hello ]`,
642+
`[attr $= world ]`,
643+
`[attr *= "llo wor" ]`,
644+
`[attr2 |= hello]`,
645+
`[attr = "Hello World" i ]`,
646+
`[attr *= "llo WOR"i]`,
647+
`[attr $= woRLD i]`,
648+
`[attr2 = "hello-''>>foo=bar[]"]`,
649+
`[attr2 $="foo=bar[]"]`,
650+
];
651+
for (const selector of selectors)
652+
expect(await page.$eval(selector, e => e === div)).toBe(true);
653+
expect(await page.$eval(`[attr*=hello] span`, e => e.parentNode === div)).toBe(true);
654+
expect(await page.$eval(`[attr*=hello] >> span`, e => e.parentNode === div)).toBe(true);
655+
expect(await page.$eval(`[attr3="] span"] >> span`, e => e.parentNode === div)).toBe(true);
656+
});
625657
});
626658

627659
describe('attribute selector', () => {

0 commit comments

Comments
 (0)