From b8a46580dd840fa246410bbd0bda1dcb04aeaede Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 14 Sep 2021 19:24:29 -0700 Subject: [PATCH] fix(expect): toHaveText, toContainText and toHaveTitle normalize whitespace (#8929) --- src/test/matchers/matchers.ts | 21 +++++++++++-------- src/test/matchers/toMatchText.ts | 12 +++++++++-- .../playwright.expect.misc.spec.ts | 4 ++-- .../playwright.expect.text.spec.ts | 17 ++++++++++----- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/test/matchers/matchers.ts b/src/test/matchers/matchers.ts index 009d9d97c8..206a37be13 100644 --- a/src/test/matchers/matchers.ts +++ b/src/test/matchers/matchers.ts @@ -15,12 +15,12 @@ */ import { Locator, Page } from '../../..'; -import { constructURLBasedOnBaseURL } from '../../utils/utils'; +import { constructURLBasedOnBaseURL, isString } from '../../utils/utils'; import { currentTestInfo } from '../globals'; import type { Expect } from '../types'; import { toBeTruthy } from './toBeTruthy'; import { toEqual } from './toEqual'; -import { toMatchText } from './toMatchText'; +import { normalizeWhiteSpace, toMatchText } from './toMatchText'; export function toBeChecked( this: ReturnType, @@ -118,7 +118,7 @@ export function toContainText( if (options?.useInnerText) return await locator.innerText({ timeout }); return await locator.textContent() || ''; - }, expected, { ...options, matchSubstring: true }); + }, expected, { ...options, matchSubstring: true, normalizeWhiteSpace: true }); } export function toHaveAttribute( @@ -202,20 +202,23 @@ export function toHaveText( this: ReturnType, locator: Locator, expected: string | RegExp | (string | RegExp)[], - options?: { timeout?: number, useInnerText?: boolean }, + options: { timeout?: number, useInnerText?: boolean } = {}, ) { if (Array.isArray(expected)) { + const expectedArray = expected.map(e => isString(e) ? normalizeWhiteSpace(e) : e); return toEqual.call(this, 'toHaveText', locator, 'Locator', async () => { - return locator.evaluateAll((ee, useInnerText) => { + const texts = await locator.evaluateAll((ee, useInnerText) => { return ee.map(e => useInnerText ? (e as HTMLElement).innerText : e.textContent || ''); }, options?.useInnerText); - }, expected, options); + // Normalize those values that have string expectations. + return texts.map((s, index) => isString(expectedArray[index]) ? normalizeWhiteSpace(s) : s); + }, expectedArray, options); } else { return toMatchText.call(this, 'toHaveText', locator, 'Locator', async timeout => { if (options?.useInnerText) return await locator.innerText({ timeout }); return await locator.textContent() || ''; - }, expected, options); + }, expected, { ...options, normalizeWhiteSpace: true }); } } @@ -223,11 +226,11 @@ export function toHaveTitle( this: ReturnType, page: Page, expected: string | RegExp, - options?: { timeout?: number }, + options: { timeout?: number } = {}, ) { return toMatchText.call(this, 'toHaveTitle', page, 'Page', async () => { return await page.title(); - }, expected, options); + }, expected, { ...options, normalizeWhiteSpace: true }); } export function toHaveURL( diff --git a/src/test/matchers/toMatchText.ts b/src/test/matchers/toMatchText.ts index 0783c728de..68b0bfec84 100644 --- a/src/test/matchers/toMatchText.ts +++ b/src/test/matchers/toMatchText.ts @@ -18,7 +18,7 @@ import { printReceivedStringContainExpectedResult, printReceivedStringContainExpectedSubstring } from 'expect/build/print'; - +import { isString } from '../../utils/utils'; import { currentTestInfo } from '../globals'; import type { Expect } from '../types'; import { expectType, pollUntilDeadline } from '../util'; @@ -30,7 +30,7 @@ export async function toMatchText( receiverType: string, query: (timeout: number) => Promise, expected: string | RegExp, - options: { timeout?: number, matchSubstring?: boolean } = {}, + options: { timeout?: number, matchSubstring?: boolean, normalizeWhiteSpace?: boolean } = {}, ) { const testInfo = currentTestInfo(); if (!testInfo) @@ -59,9 +59,13 @@ export async function toMatchText( let received: string; let pass = false; + if (options.normalizeWhiteSpace && isString(expected)) + expected = normalizeWhiteSpace(expected); await pollUntilDeadline(testInfo, async remainingTime => { received = await query(remainingTime); + if (options.normalizeWhiteSpace && isString(expected)) + received = normalizeWhiteSpace(received); if (options.matchSubstring) pass = received.includes(expected as string); else if (typeof expected === 'string') @@ -112,3 +116,7 @@ export async function toMatchText( return { message, pass }; } + +export function normalizeWhiteSpace(s: string) { + return s.trim().replace(/\s+/g, ' '); +} diff --git a/tests/playwright-test/playwright.expect.misc.spec.ts b/tests/playwright-test/playwright.expect.misc.spec.ts index faf3a0df6f..8362f1dc5b 100644 --- a/tests/playwright-test/playwright.expect.misc.spec.ts +++ b/tests/playwright-test/playwright.expect.misc.spec.ts @@ -124,8 +124,8 @@ test('should support toHaveTitle', async ({ runInlineTest }) => { const { test } = pwt; test('pass', async ({ page }) => { - await page.setContent('Hello'); - await expect(page).toHaveTitle('Hello'); + await page.setContent(' Hello world'); + await expect(page).toHaveTitle('Hello world'); }); test('fail', async ({ page }) => { diff --git a/tests/playwright-test/playwright.expect.text.spec.ts b/tests/playwright-test/playwright.expect.text.spec.ts index 6f5e4b2c32..1e29e4fbc2 100644 --- a/tests/playwright-test/playwright.expect.text.spec.ts +++ b/tests/playwright-test/playwright.expect.text.spec.ts @@ -22,9 +22,12 @@ test('should support toHaveText w/ regex', async ({ runInlineTest }) => { const { test } = pwt; test('pass', async ({ page }) => { - await page.setContent('
Text content
'); + await page.setContent('
Text content
'); const locator = page.locator('#node'); await expect(locator).toHaveText(/Text/); + + // Should not normalize whitespace. + await expect(locator).toHaveText(/Text content/); }); test('fail', async ({ page }) => { @@ -50,15 +53,18 @@ test('should support toHaveText w/ text', async ({ runInlineTest }) => { const { test } = pwt; test('pass', async ({ page }) => { - await page.setContent('
Text content
'); + await page.setContent('
Text \\ncontent 
'); const locator = page.locator('#node'); - await expect(locator).toHaveText('Text content'); + // Should normalize whitespace. + await expect(locator).toHaveText('Text content'); }); test('pass contain', async ({ page }) => { await page.setContent('
Text content
'); const locator = page.locator('#node'); await expect(locator).toContainText('Text'); + // Should normalize whitespace. + await expect(locator).toContainText(' Text content\\n '); }); test('fail', async ({ page }) => { @@ -84,9 +90,10 @@ test('should support toHaveText w/ array', async ({ runInlineTest }) => { const { test } = pwt; test('pass', async ({ page }) => { - await page.setContent('
Text 1
Text 2a
'); + await page.setContent('
Text \\n1
Text 2a
'); const locator = page.locator('div'); - await expect(locator).toHaveText(['Text 1', /Text \\d+a/]); + // Should only normalize whitespace in the first item. + await expect(locator).toHaveText(['Text 1', /Text \\d+a/]); }); test('fail', async ({ page }) => {