mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(expect): add ignoreCase option to toHaveText and toContainText (#14534)
This commit is contained in:
parent
66fc04cdb3
commit
d00efa0dfe
@ -158,6 +158,11 @@ The opposite of [`method: LocatorAssertions.toContainText`].
|
||||
|
||||
Expected substring or RegExp or a list of those.
|
||||
|
||||
### option: LocatorAssertions.NotToContainText.ignoreCase
|
||||
- `ignoreCase` <[boolean]>
|
||||
|
||||
Whether to perform case-insensitive match. [`option: ignoreCase`] option takes precedence over the corresponding regular expression flag if specified.
|
||||
|
||||
### option: LocatorAssertions.NotToContainText.useInnerText
|
||||
- `useInnerText` <[boolean]>
|
||||
|
||||
@ -269,6 +274,11 @@ The opposite of [`method: LocatorAssertions.toHaveText`].
|
||||
|
||||
Expected substring or RegExp or a list of those.
|
||||
|
||||
### option: LocatorAssertions.NotToHaveText.ignoreCase
|
||||
- `ignoreCase` <[boolean]>
|
||||
|
||||
Whether to perform case-insensitive match. [`option: ignoreCase`] option takes precedence over the corresponding regular expression flag if specified.
|
||||
|
||||
### option: LocatorAssertions.NotToHaveText.useInnerText
|
||||
- `useInnerText` <[boolean]>
|
||||
|
||||
@ -685,6 +695,11 @@ Expected substring or RegExp or a list of those.
|
||||
|
||||
Expected substring or RegExp or a list of those.
|
||||
|
||||
### option: LocatorAssertions.toContainText.ignoreCase
|
||||
- `ignoreCase` <[boolean]>
|
||||
|
||||
Whether to perform case-insensitive match. [`option: ignoreCase`] option takes precedence over the corresponding regular expression flag if specified.
|
||||
|
||||
### option: LocatorAssertions.toContainText.useInnerText
|
||||
- `useInnerText` <[boolean]>
|
||||
|
||||
@ -1136,6 +1151,11 @@ Expected substring or RegExp or a list of those.
|
||||
|
||||
Expected substring or RegExp or a list of those.
|
||||
|
||||
### option: LocatorAssertions.toHaveText.ignoreCase
|
||||
- `ignoreCase` <[boolean]>
|
||||
|
||||
Whether to perform case-insensitive match. [`option: ignoreCase`] option takes precedence over the corresponding regular expression flag if specified.
|
||||
|
||||
### option: LocatorAssertions.toHaveText.useInnerText
|
||||
- `useInnerText` <[boolean]>
|
||||
|
||||
|
||||
@ -187,6 +187,7 @@ export type ExpectedTextValue = {
|
||||
regexSource?: string,
|
||||
regexFlags?: string,
|
||||
matchSubstring?: boolean,
|
||||
ignoreCase?: boolean,
|
||||
normalizeWhiteSpace?: boolean,
|
||||
};
|
||||
|
||||
|
||||
@ -108,6 +108,7 @@ ExpectedTextValue:
|
||||
regexSource: string?
|
||||
regexFlags: string?
|
||||
matchSubstring: boolean?
|
||||
ignoreCase: boolean?
|
||||
normalizeWhiteSpace: boolean?
|
||||
|
||||
|
||||
|
||||
@ -84,6 +84,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
regexSource: tOptional(tString),
|
||||
regexFlags: tOptional(tString),
|
||||
matchSubstring: tOptional(tBoolean),
|
||||
ignoreCase: tOptional(tBoolean),
|
||||
normalizeWhiteSpace: tOptional(tBoolean),
|
||||
});
|
||||
scheme.AXNode = tObject({
|
||||
|
||||
@ -1231,17 +1231,26 @@ class ExpectedTextMatcher {
|
||||
private _substring: string | undefined;
|
||||
private _regex: RegExp | undefined;
|
||||
private _normalizeWhiteSpace: boolean | undefined;
|
||||
private _ignoreCase: boolean | undefined;
|
||||
|
||||
constructor(expected: channels.ExpectedTextValue) {
|
||||
this._normalizeWhiteSpace = expected.normalizeWhiteSpace;
|
||||
this._string = expected.matchSubstring ? undefined : this.normalizeWhiteSpace(expected.string);
|
||||
this._substring = expected.matchSubstring ? this.normalizeWhiteSpace(expected.string) : undefined;
|
||||
this._regex = expected.regexSource ? new RegExp(expected.regexSource, expected.regexFlags) : undefined;
|
||||
this._ignoreCase = expected.ignoreCase;
|
||||
this._string = expected.matchSubstring ? undefined : this.normalize(expected.string);
|
||||
this._substring = expected.matchSubstring ? this.normalize(expected.string) : undefined;
|
||||
if (expected.regexSource) {
|
||||
const flags = new Set((expected.regexFlags || '').split(''));
|
||||
if (expected.ignoreCase === false)
|
||||
flags.delete('i');
|
||||
if (expected.ignoreCase === true)
|
||||
flags.add('i');
|
||||
this._regex = new RegExp(expected.regexSource, [...flags].join(''));
|
||||
}
|
||||
}
|
||||
|
||||
matches(text: string): boolean {
|
||||
if (this._normalizeWhiteSpace && !this._regex)
|
||||
text = this.normalizeWhiteSpace(text)!;
|
||||
if (!this._regex)
|
||||
text = this.normalize(text)!;
|
||||
if (this._string !== undefined)
|
||||
return text === this._string;
|
||||
if (this._substring !== undefined)
|
||||
@ -1251,10 +1260,14 @@ class ExpectedTextMatcher {
|
||||
return false;
|
||||
}
|
||||
|
||||
private normalizeWhiteSpace(s: string | undefined): string | undefined {
|
||||
private normalize(s: string | undefined): string | undefined {
|
||||
if (!s)
|
||||
return s;
|
||||
return this._normalizeWhiteSpace ? s.trim().replace(/\u200b/g, '').replace(/\s+/g, ' ') : s;
|
||||
if (this._normalizeWhiteSpace)
|
||||
s = s.trim().replace(/\u200b/g, '').replace(/\s+/g, ' ');
|
||||
if (this._ignoreCase)
|
||||
s = s.toLocaleLowerCase();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -117,17 +117,17 @@ export function toContainText(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: LocatorEx,
|
||||
expected: string | RegExp | (string | RegExp)[],
|
||||
options?: { timeout?: number, useInnerText?: boolean },
|
||||
options: { timeout?: number, useInnerText?: boolean, ignoreCase?: boolean } = {},
|
||||
) {
|
||||
if (Array.isArray(expected)) {
|
||||
return toEqual.call(this, 'toContainText', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
|
||||
const expectedText = toExpectedTextValues(expected, { matchSubstring: true, normalizeWhiteSpace: true });
|
||||
return await locator._expect(customStackTrace, 'to.contain.text.array', { expectedText, isNot, useInnerText: options?.useInnerText, timeout });
|
||||
const expectedText = toExpectedTextValues(expected, { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
|
||||
return await locator._expect(customStackTrace, 'to.contain.text.array', { expectedText, isNot, useInnerText: options.useInnerText, timeout });
|
||||
}, expected, { ...options, contains: true });
|
||||
} else {
|
||||
return toMatchText.call(this, 'toContainText', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
|
||||
const expectedText = toExpectedTextValues([expected], { matchSubstring: true, normalizeWhiteSpace: true });
|
||||
return await locator._expect(customStackTrace, 'to.have.text', { expectedText, isNot, useInnerText: options?.useInnerText, timeout });
|
||||
const expectedText = toExpectedTextValues([expected], { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
|
||||
return await locator._expect(customStackTrace, 'to.have.text', { expectedText, isNot, useInnerText: options.useInnerText, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
}
|
||||
@ -216,16 +216,16 @@ export function toHaveText(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: LocatorEx,
|
||||
expected: string | RegExp | (string | RegExp)[],
|
||||
options: { timeout?: number, useInnerText?: boolean } = {},
|
||||
options: { timeout?: number, useInnerText?: boolean, ignoreCase?: boolean } = {},
|
||||
) {
|
||||
if (Array.isArray(expected)) {
|
||||
return toEqual.call(this, 'toHaveText', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
|
||||
const expectedText = toExpectedTextValues(expected, { normalizeWhiteSpace: true });
|
||||
const expectedText = toExpectedTextValues(expected, { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
|
||||
return await locator._expect(customStackTrace, 'to.have.text.array', { expectedText, isNot, useInnerText: options?.useInnerText, timeout });
|
||||
}, expected, options);
|
||||
} else {
|
||||
return toMatchText.call(this, 'toHaveText', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
|
||||
const expectedText = toExpectedTextValues([expected], { normalizeWhiteSpace: true });
|
||||
const expectedText = toExpectedTextValues([expected], { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
|
||||
return await locator._expect(customStackTrace, 'to.have.text', { expectedText, isNot, useInnerText: options?.useInnerText, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
|
||||
@ -101,12 +101,13 @@ export async function toMatchText(
|
||||
return { message, pass };
|
||||
}
|
||||
|
||||
export function toExpectedTextValues(items: (string | RegExp)[], options: { matchSubstring?: boolean, normalizeWhiteSpace?: boolean } = {}): ExpectedTextValue[] {
|
||||
export function toExpectedTextValues(items: (string | RegExp)[], options: { matchSubstring?: boolean, normalizeWhiteSpace?: boolean, ignoreCase?: boolean } = {}): ExpectedTextValue[] {
|
||||
return items.map(i => ({
|
||||
string: isString(i) ? i : undefined,
|
||||
regexSource: isRegExp(i) ? i.source : undefined,
|
||||
regexFlags: isRegExp(i) ? i.flags : undefined,
|
||||
matchSubstring: options.matchSubstring,
|
||||
ignoreCase: options.ignoreCase,
|
||||
normalizeWhiteSpace: options.normalizeWhiteSpace,
|
||||
}));
|
||||
}
|
||||
|
||||
12
packages/playwright-test/types/test.d.ts
vendored
12
packages/playwright-test/types/test.d.ts
vendored
@ -3197,6 +3197,12 @@ interface LocatorAssertions {
|
||||
* @param options
|
||||
*/
|
||||
toContainText(expected: string|RegExp|Array<string|RegExp>, options?: {
|
||||
/**
|
||||
* Whether to perform case-insensitive match. `ignoreCase` option takes precedence over the corresponding regular
|
||||
* expression flag if specified.
|
||||
*/
|
||||
ignoreCase?: boolean;
|
||||
|
||||
/**
|
||||
* Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`.
|
||||
*/
|
||||
@ -3496,6 +3502,12 @@ interface LocatorAssertions {
|
||||
* @param options
|
||||
*/
|
||||
toHaveText(expected: string|RegExp|Array<string|RegExp>, options?: {
|
||||
/**
|
||||
* Whether to perform case-insensitive match. `ignoreCase` option takes precedence over the corresponding regular
|
||||
* expression flag if specified.
|
||||
*/
|
||||
ignoreCase?: boolean;
|
||||
|
||||
/**
|
||||
* Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`.
|
||||
*/
|
||||
|
||||
@ -28,6 +28,10 @@ test('should support toHaveText w/ regex', async ({ runInlineTest }) => {
|
||||
|
||||
// Should not normalize whitespace.
|
||||
await expect(locator).toHaveText(/Text content/);
|
||||
// Should respect ignoreCase.
|
||||
await expect(locator).toHaveText(/text content/, { ignoreCase: true });
|
||||
// Should override regex flag with ignoreCase.
|
||||
await expect(locator).not.toHaveText(/text content/i, { ignoreCase: false });
|
||||
});
|
||||
|
||||
test('fail', async ({ page }) => {
|
||||
@ -90,6 +94,10 @@ test('should support toHaveText w/ text', async ({ runInlineTest }) => {
|
||||
await expect(locator).toHaveText('Text content');
|
||||
// Should normalize zero width whitespace.
|
||||
await expect(locator).toHaveText('T\u200be\u200bx\u200bt content');
|
||||
// Should support ignoreCase.
|
||||
await expect(locator).toHaveText('text CONTENT', { ignoreCase: true });
|
||||
// Should support falsy ignoreCase.
|
||||
await expect(locator).not.toHaveText('TEXT', { ignoreCase: false });
|
||||
});
|
||||
|
||||
test('pass contain', async ({ page }) => {
|
||||
@ -98,6 +106,10 @@ test('should support toHaveText w/ text', async ({ runInlineTest }) => {
|
||||
await expect(locator).toContainText('Text');
|
||||
// Should normalize whitespace.
|
||||
await expect(locator).toContainText(' ext cont\\n ');
|
||||
// Should support ignoreCase.
|
||||
await expect(locator).toContainText('EXT', { ignoreCase: true });
|
||||
// Should support falsy ignoreCase.
|
||||
await expect(locator).not.toContainText('TEXT', { ignoreCase: false });
|
||||
});
|
||||
|
||||
test('fail', async ({ page }) => {
|
||||
@ -126,6 +138,8 @@ test('should support toHaveText w/ not', async ({ runInlineTest }) => {
|
||||
await page.setContent('<div id=node>Text content</div>');
|
||||
const locator = page.locator('#node');
|
||||
await expect(locator).not.toHaveText('Text2');
|
||||
// Should be case-sensitive by default.
|
||||
await expect(locator).not.toHaveText('TEXT');
|
||||
});
|
||||
|
||||
test('fail', async ({ page }) => {
|
||||
@ -155,6 +169,8 @@ test('should support toHaveText w/ array', async ({ runInlineTest }) => {
|
||||
const locator = page.locator('div');
|
||||
// Should only normalize whitespace in the first item.
|
||||
await expect(locator).toHaveText(['Text 1', /Text \\d+a/]);
|
||||
// Should support ignoreCase.
|
||||
await expect(locator).toHaveText(['tEXT 1', 'TExt 2A'], { ignoreCase: true });
|
||||
});
|
||||
|
||||
test('pass lazy', async ({ page }) => {
|
||||
@ -228,6 +244,8 @@ test('should support toContainText w/ array', async ({ runInlineTest }) => {
|
||||
await page.setContent('<div>Text \\n1</div><div>Text2</div><div>Text3</div>');
|
||||
const locator = page.locator('div');
|
||||
await expect(locator).toContainText(['ext 1', /ext3/]);
|
||||
// Should support ignoreCase.
|
||||
await expect(locator).toContainText(['EXT 1', 'eXt3'], { ignoreCase: true });
|
||||
});
|
||||
|
||||
test('fail', async ({ page }) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user