From 92bbdbed6511f5b7340664a3b3d593c4741f66da Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Wed, 24 Feb 2021 19:52:30 -0800 Subject: [PATCH] cherry-pick(release-1.9): make quoted selector match by text nodes (#5603) (#5608) Cherry-pick 0102e080f600e02ea0f44ea2735ba6c0a5e1795d Fixes #5583 Co-authored-by: Dmitry Gozman --- docs/src/selectors.md | 2 +- src/server/injected/injectedScript.ts | 4 ++-- src/server/injected/selectorEvaluator.ts | 10 ++++---- test/selectors-text.spec.ts | 30 +++++++++++++++++------- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/docs/src/selectors.md b/docs/src/selectors.md index 0004a261a1e49..534d3404a0ca9 100644 --- a/docs/src/selectors.md +++ b/docs/src/selectors.md @@ -148,7 +148,7 @@ Text selector has a few variations: page.click("text=Log in") ``` -- `text="Log in"` - text body can be escaped with single or double quotes for full-string case-sensitive match. For example `text="Log"` does not match `` but instead matches `Log`. +- `text="Log in"` - text body can be escaped with single or double quotes for case-sensitive match. For example `text="Log"` does not match `` but instead matches `Log in`. Quoted body follows the usual escaping rules, e.g. use `\"` to escape double quote in a double-quoted string: `text="foo\"bar"`. diff --git a/src/server/injected/injectedScript.ts b/src/server/injected/injectedScript.ts index 562d152dfd4e5..1b2cfe35b9ea7 100644 --- a/src/server/injected/injectedScript.ts +++ b/src/server/injected/injectedScript.ts @@ -780,8 +780,8 @@ function createTextMatcher(selector: string): { matcher: Matcher, strict: boolea const matcher = (text: string) => { text = text.trim().replace(/\s+/g, ' '); if (!strict) - return text.toLowerCase().includes(selector); - return text === selector; + text = text.toLowerCase(); + return text.includes(selector); }; return { matcher, strict }; } diff --git a/src/server/injected/selectorEvaluator.ts b/src/server/injected/selectorEvaluator.ts index cdbdbe023fda6..b7ff329178211 100644 --- a/src/server/injected/selectorEvaluator.ts +++ b/src/server/injected/selectorEvaluator.ts @@ -462,13 +462,15 @@ const hasTextEngine: SelectorEngine = { }, }; -function textMatcher(text: string, substring: boolean): (s: string) => boolean { +function textMatcher(text: string, caseInsensitive: boolean): (s: string) => boolean { text = text.trim().replace(/\s+/g, ' '); - text = text.toLowerCase(); + if (caseInsensitive) + text = text.toLowerCase(); return (s: string) => { s = s.trim().replace(/\s+/g, ' '); - s = s.toLowerCase(); - return substring ? s.includes(text) : s === text; + if (caseInsensitive) + s = s.toLowerCase(); + return s.includes(text); }; } diff --git a/test/selectors-text.spec.ts b/test/selectors-text.spec.ts index 4be81595bb61e..e22ccd9a987d8 100644 --- a/test/selectors-text.spec.ts +++ b/test/selectors-text.spec.ts @@ -103,9 +103,9 @@ it('should work', async ({page}) => { expect((await page.$$(`text="Sign in"`)).length).toBe(1); expect(await page.$eval(`text=lo wo`, e => e.outerHTML)).toBe('Hello\n \nworld'); expect(await page.$eval(`text="Hello world"`, e => e.outerHTML)).toBe('Hello\n \nworld'); - expect(await page.$(`text="lo wo"`)).toBe(null); + expect(await page.$eval(`text="lo wo"`, e => e.outerHTML)).toBe('Hello\n \nworld'); expect((await page.$$(`text=lo \nwo`)).length).toBe(1); - expect((await page.$$(`text="lo wo"`)).length).toBe(0); + expect((await page.$$(`text="lo \nwo"`)).length).toBe(1); }); it('should work with :text', async ({page}) => { @@ -113,9 +113,9 @@ it('should work with :text', async ({page}) => { expect(await page.$eval(`:text("ya")`, e => e.outerHTML)).toBe('
ya
'); expect(await page.$eval(`:text-is("ya")`, e => e.outerHTML)).toBe('
ya
'); expect(await page.$eval(`:text("y")`, e => e.outerHTML)).toBe('
yo
'); - expect(await page.$(`:text-is("y")`)).toBe(null); + expect(await page.$(`:text-is("Y")`)).toBe(null); expect(await page.$eval(`:text("hello world")`, e => e.outerHTML)).toBe('
\nHELLO \n world
'); - expect(await page.$eval(`:text-is("hello world")`, e => e.outerHTML)).toBe('
\nHELLO \n world
'); + expect(await page.$eval(`:text-is("HELLO world")`, e => e.outerHTML)).toBe('
\nHELLO \n world
'); expect(await page.$eval(`:text("lo wo")`, e => e.outerHTML)).toBe('
\nHELLO \n world
'); expect(await page.$(`:text-is("lo wo")`)).toBe(null); expect(await page.$eval(`:text-matches("^[ay]+$")`, e => e.outerHTML)).toBe('
ya
'); @@ -145,11 +145,11 @@ it('should work across nodes', async ({page}) => { expect(await page.$(`text=hello world`)).toBe(null); expect(await page.$eval(`:text-is("Hello, world!")`, e => e.id)).toBe('target1'); - expect(await page.$(`:text-is("Hello")`)).toBe(null); + expect(await page.$eval(`:text-is("Hello")`, e => e.id)).toBe('target1'); expect(await page.$eval(`:text-is("world")`, e => e.id)).toBe('target2'); expect(await page.$$eval(`:text-is("world")`, els => els.length)).toBe(1); expect(await page.$eval(`text="Hello, world!"`, e => e.id)).toBe('target1'); - expect(await page.$(`text="Hello"`)).toBe(null); + expect(await page.$eval(`text="Hello"`, e => e.id)).toBe('target1'); expect(await page.$eval(`text="world"`, e => e.id)).toBe('target2'); expect(await page.$$eval(`text="world"`, els => els.length)).toBe(1); @@ -162,6 +162,20 @@ it('should work across nodes', async ({page}) => { expect(await page.$$eval(`text=/world/`, els => els.length)).toBe(1); }); +it('should work with text nodes in quoted mode', async ({page}) => { + await page.setContent(`
Hellowo rld Hi again
`); + expect(await page.$eval(`text="Hello"`, e => e.id)).toBe('target1'); + expect(await page.$eval(`text="Hi again"`, e => e.id)).toBe('target1'); + expect(await page.$eval(`text="wo rld"`, e => e.id)).toBe('target2'); + expect(await page.$eval(`text="Hellowo rld Hi again"`, e => e.id)).toBe('target1'); + expect(await page.$eval(`text="Hellowo"`, e => e.id)).toBe('target1'); + expect(await page.$eval(`text="Hellowo rld"`, e => e.id)).toBe('target1'); + expect(await page.$eval(`text="wo rld Hi ag"`, e => e.id)).toBe('target1'); + expect(await page.$eval(`text="again"`, e => e.id)).toBe('target1'); + expect(await page.$(`text="hi again"`)).toBe(null); + expect(await page.$eval(`text=hi again`, e => e.id)).toBe('target1'); +}); + it('should clear caches', async ({page}) => { await page.setContent(`
text
text
`); const div = await page.$('#target1'); @@ -277,10 +291,10 @@ it('should be case sensitive if quotes are specified', async ({page}) => { expect(await page.$(`text="yA"`)).toBe(null); }); -it('should search for a substring without quotes', async ({page}) => { +it('should search for a substring', async ({page}) => { await page.setContent(`
textwithsubstring
`); expect(await page.$eval(`text=with`, e => e.outerHTML)).toBe('
textwithsubstring
'); - expect(await page.$(`text="with"`)).toBe(null); + expect(await page.$eval(`text="with"`, e => e.outerHTML)).toBe('
textwithsubstring
'); }); it('should skip head, script and style', async ({page}) => {