Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions packages/injected/src/injectedScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1723,8 +1723,12 @@ function oneLine(s: string): string {

function createAttributeMatcher(part: AttributeSelectorPart): (s: string) => boolean {
const { value, caseSensitive } = part;
if (value instanceof RegExp)
return s => !!s.match(value);
if (value instanceof RegExp) {
return s => {
value.lastIndex = 0;
return !!s.match(value);
};
}
if (caseSensitive)
return s => s === value;
const lowerCaseValue = value.toLowerCase();
Expand All @@ -1750,7 +1754,11 @@ function createTextMatcher(selector: string, internal: boolean): { matcher: Text
if (selector[0] === '/' && selector.lastIndexOf('/') > 0) {
const lastSlash = selector.lastIndexOf('/');
const re = new RegExp(selector.substring(1, lastSlash), selector.substring(lastSlash + 1));
return { matcher: (elementText: ElementText) => re.test(elementText.full), kind: 'regex' };
const matcher = (elementText: ElementText) => {
re.lastIndex = 0;
return re.test(elementText.full);
};
return { matcher, kind: 'regex' };
}
const unquote = internal ? JSON.parse.bind(JSON) : cssUnquote;
let strict = false;
Expand Down Expand Up @@ -1812,8 +1820,10 @@ class ExpectedTextMatcher {
return text === this._string;
if (this._substring !== undefined)
return text.includes(this._substring);
if (this._regex)
if (this._regex) {
this._regex.lastIndex = 0;
return !!this._regex.test(text);
}
return false;
}

Expand Down
5 changes: 4 additions & 1 deletion packages/injected/src/selectorEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,10 @@ const textMatchesEngine: SelectorEngine = {
if (args.length === 0 || typeof args[0] !== 'string' || args.length > 2 || (args.length === 2 && typeof args[1] !== 'string'))
throw new Error(`"text-matches" engine expects a regexp body and optional regexp flags`);
const re = new RegExp(args[0], args.length === 2 ? args[1] : undefined);
const matcher = (elementText: ElementText) => re.test(elementText.full);
const matcher = (elementText: ElementText) => {
re.lastIndex = 0;
return re.test(elementText.full);
};
return elementMatchesText((evaluator as SelectorEvaluatorImpl)._cacheText, element, matcher) === 'self';
},
};
Expand Down
4 changes: 3 additions & 1 deletion packages/injected/src/selectorUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ export function matchesAttributePart(value: any, attr: AttributeSelectorPart) {
if (attr.op === '<truthy>')
return !!objValue;
if (attr.op === '=') {
if (attrValue instanceof RegExp)
if (attrValue instanceof RegExp) {
attrValue.lastIndex = 0;
return typeof objValue === 'string' && !!objValue.match(attrValue);
}
return objValue === attrValue;
}
if (typeof objValue !== 'string' || typeof attrValue !== 'string')
Expand Down
21 changes: 21 additions & 0 deletions tests/page/selectors-get-by.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,24 @@ it('getByRole with description whitespace normalization', async ({ page }) => {
'Alert',
]);
});

it('locators should not be affected by global and sticky regex flags', async ({ page }) => {
await page.setContent(`
<div>foo</div><div>foo</div><div>foo</div>
<label>foo<input></label><label>foo<input></label><label>foo<input></label>
<button>foo</button><button>foo</button><button>foo</button>
<input placeholder=foo><input placeholder=foo><input placeholder=foo>
`);
await expect(page.getByText(/foo/)).toHaveCount(9);
await expect(page.getByText(/foo/g)).toHaveCount(9);
await expect(page.getByText(/foo/y)).toHaveCount(9);
await expect(page.getByLabel(/foo/)).toHaveCount(3);
await expect(page.getByLabel(/foo/g)).toHaveCount(3);
await expect(page.getByLabel(/foo/y)).toHaveCount(3);
await expect(page.getByRole('button', { name: /foo/ })).toHaveCount(3);
await expect(page.getByRole('button', { name: /foo/g })).toHaveCount(3);
await expect(page.getByRole('button', { name: /foo/y })).toHaveCount(3);
await expect(page.getByPlaceholder(/foo/)).toHaveCount(3);
await expect(page.getByPlaceholder(/foo/g)).toHaveCount(3);
await expect(page.getByPlaceholder(/foo/y)).toHaveCount(3);
});
Loading