diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index b7a395cfed..8aedc6f0b1 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -33,7 +33,7 @@ import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName, get import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; import type { Language } from '../../utils/isomorphic/locatorGenerators'; -import { normalizeWhiteSpace, trimStringWithEllipsis } from '../../utils/isomorphic/stringUtils'; +import { cacheNormalizedWhitespaces, normalizeWhiteSpace, trimStringWithEllipsis } from '../../utils/isomorphic/stringUtils'; export type FrameExpectParams = Omit & { expectedValue?: any }; @@ -66,7 +66,7 @@ export class InjectedScript { // eslint-disable-next-line no-restricted-globals readonly window: Window & typeof globalThis; readonly document: Document; - readonly utils = { isInsideScope, elementText, asLocator, normalizeWhiteSpace }; + readonly utils = { isInsideScope, elementText, asLocator, normalizeWhiteSpace, cacheNormalizedWhitespaces }; // eslint-disable-next-line no-restricted-globals constructor(window: Window & typeof globalThis, isUnderTest: boolean, sdkLanguage: Language, testIdAttributeNameForStrictErrorAndConsoleCodegen: string, stableRafCount: number, browserName: string, customEngines: { name: string, engine: SelectorEngine }[]) { diff --git a/packages/playwright-core/src/server/injected/recorder/recorder.ts b/packages/playwright-core/src/server/injected/recorder/recorder.ts index 30af352b45..261a0eab21 100644 --- a/packages/playwright-core/src/server/injected/recorder/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder/recorder.ts @@ -973,7 +973,7 @@ export class Recorder { body[data-pw-cursor=text] *, body[data-pw-cursor=text] *::after { cursor: text !important; } `); this.installListeners(); - + injectedScript.utils.cacheNormalizedWhitespaces(); if (injectedScript.isUnderTest) console.error('Recorder script ready for test'); // eslint-disable-line no-console } diff --git a/packages/playwright-core/src/utils/isomorphic/stringUtils.ts b/packages/playwright-core/src/utils/isomorphic/stringUtils.ts index 19953ef5bb..df213a1085 100644 --- a/packages/playwright-core/src/utils/isomorphic/stringUtils.ts +++ b/packages/playwright-core/src/utils/isomorphic/stringUtils.ts @@ -67,8 +67,19 @@ function cssEscapeOne(s: string, i: number): string { return '\\' + s.charAt(i); } +let normalizedWhitespaceCache: Map | undefined; + +export function cacheNormalizedWhitespaces() { + normalizedWhitespaceCache = new Map(); +} + export function normalizeWhiteSpace(text: string): string { - return text.replace(/\u200b/g, '').trim().replace(/\s+/g, ' '); + let result = normalizedWhitespaceCache?.get(text); + if (result === undefined) { + result = text.replace(/\u200b/g, '').trim().replace(/\s+/g, ' '); + normalizedWhitespaceCache?.set(text, result); + } + return result; } export function normalizeEscapedRegexQuotes(source: string) {