diff --git a/packages/playwright-core/src/utils/isomorphic/locatorUtils.ts b/packages/playwright-core/src/utils/isomorphic/locatorUtils.ts index 51b51cc410..3da73a4922 100644 --- a/packages/playwright-core/src/utils/isomorphic/locatorUtils.ts +++ b/packages/playwright-core/src/utils/isomorphic/locatorUtils.ts @@ -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)}]`; } diff --git a/packages/playwright-core/src/utils/isomorphic/stringUtils.ts b/packages/playwright-core/src/utils/isomorphic/stringUtils.ts index 07d1544853..9602dc6390 100644 --- a/packages/playwright-core/src/utils/isomorphic/stringUtils.ts +++ b/packages/playwright-core/src/utils/isomorphic/stringUtils.ts @@ -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, diff --git a/tests/page/selectors-get-by.spec.ts b/tests/page/selectors-get-by.spec.ts index a95edc8900..6d19a43849 100644 --- a/tests/page/selectors-get-by.spec.ts +++ b/tests/page/selectors-get-by.spec.ts @@ -177,12 +177,12 @@ wo"rld`); 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(``); @@ -191,12 +191,12 @@ world`); 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(`
Text here
`); await expect.soft(page.getByTitle('my title', { exact: true })).toHaveCount(1, { timeout: 500 }); @@ -204,6 +204,25 @@ world`); 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(``); + 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 }) => { diff --git a/tests/page/selectors-text.spec.ts b/tests/page/selectors-text.spec.ts index 5cad1fc484..29c7e5474f 100644 --- a/tests/page/selectors-text.spec.ts +++ b/tests/page/selectors-text.spec.ts @@ -76,6 +76,7 @@ it('should work @smoke', async ({ page }) => { await page.setContent(`
Hi>>
`); expect(await page.$eval(`text="Hi>>">>span`, e => e.outerHTML)).toBe(``); + expect(await page.$eval(`text=/Hi\\>\\>/ >> span`, e => e.outerHTML)).toBe(``); await page.setContent(`
a
b
a
`); expect(await page.$eval(`text=a`, e => e.outerHTML)).toBe('
a
b
');