diff --git a/src/server/frames.ts b/src/server/frames.ts index 8d4045bf2eef9..2198d549c6f45 100644 --- a/src/server/frames.ts +++ b/src/server/frames.ts @@ -1248,6 +1248,7 @@ export class Frame extends SdkObject { const data = this._contextData.get(options.mainWorld ? 'main' : info.world)!; return controller.run(async progress => { + progress.log(`waiting for selector "${selector}"`); const rerunnableTask = new RerunnableTask(data, progress, injectedScript => { return injectedScript.evaluateHandle((injected, { info, taskData, callbackText, querySelectorAll, logScale }) => { const callback = injected.eval(callbackText) as DomTaskBody; diff --git a/src/test/matchers/toMatchText.ts b/src/test/matchers/toMatchText.ts index fa71fc3baf997..a7280c5f2ba8b 100644 --- a/src/test/matchers/toMatchText.ts +++ b/src/test/matchers/toMatchText.ts @@ -65,6 +65,7 @@ export async function toMatchText( const { pass, received, log } = await query(this.isNot, timeout); const stringSubstring = options.matchSubstring ? 'substring' : 'string'; + const receivedString = received || ''; const message = pass ? () => typeof expected === 'string' @@ -72,17 +73,17 @@ export async function toMatchText( '\n\n' + `Expected ${stringSubstring}: not ${this.utils.printExpected(expected)}\n` + `Received string: ${printReceivedStringContainExpectedSubstring( - received!, - received!.indexOf(expected), + receivedString, + receivedString.indexOf(expected), expected.length, - )}` + )}` + callLogText(log) : this.utils.matcherHint(matcherName, undefined, undefined, matcherOptions) + '\n\n' + `Expected pattern: not ${this.utils.printExpected(expected)}\n` + `Received string: ${printReceivedStringContainExpectedResult( - received!, + receivedString, typeof expected.exec === 'function' - ? expected.exec(received!) + ? expected.exec(receivedString) : null, )}` + callLogText(log) : () => { @@ -95,7 +96,7 @@ export async function toMatchText( '\n\n' + this.utils.printDiffOrStringify( expected, - received, + receivedString, labelExpected, labelReceived, this.expand !== false, @@ -105,10 +106,6 @@ export async function toMatchText( return { message, pass }; } -export function normalizeWhiteSpace(s: string) { - return s.trim().replace(/\s+/g, ' '); -} - export function toExpectedTextValues(items: (string | RegExp)[], options: { matchSubstring?: boolean, normalizeWhiteSpace?: boolean } = {}): ExpectedTextValue[] { return items.map(i => ({ string: isString(i) ? i : undefined, diff --git a/tests/inspector/pause.spec.ts b/tests/inspector/pause.spec.ts index d1a096845c9cd..04cc6c34d0314 100644 --- a/tests/inspector/pause.spec.ts +++ b/tests/inspector/pause.spec.ts @@ -225,6 +225,7 @@ it.describe('pause', () => { expect(await sanitizeLog(recorderPage)).toEqual([ 'page.pause- XXms', 'page.isChecked(button)- XXms', + 'waiting for selector "button"', 'selector resolved to ', 'error: Not a checkbox or radio button', ]); diff --git a/tests/page/locator-convenience.spec.ts b/tests/page/locator-convenience.spec.ts index ddc96fc01db80..90220533a3e1d 100644 --- a/tests/page/locator-convenience.spec.ts +++ b/tests/page/locator-convenience.spec.ts @@ -81,6 +81,13 @@ it('innerText should throw', async ({ page, server }) => { expect(error2.message).toContain('Not an HTMLElement'); }); +it('innerText should produce log', async ({ page, server }) => { + await page.setContent(`
Hello
`); + const locator = page.locator('span'); + const error = await locator.innerText({ timeout: 1000 }).catch(e => e); + expect(error.message).toContain('waiting for selector "span"'); +}); + it('textContent should work', async ({ page, server }) => { await page.goto(`${server.PREFIX}/dom.html`); const locator = page.locator('#inner'); diff --git a/tests/playwright-test/playwright.expect.text.spec.ts b/tests/playwright-test/playwright.expect.text.spec.ts index 06f25e3f046b5..c46190915d718 100644 --- a/tests/playwright-test/playwright.expect.text.spec.ts +++ b/tests/playwright-test/playwright.expect.text.spec.ts @@ -312,5 +312,49 @@ test('should print nice error for toHaveText', async ({ runInlineTest }) => { expect(output).toContain('Pending operations:'); expect(output).toContain('Error: expect(received).toHaveText(expected)'); expect(output).toContain('Expected string: "Text"'); - expect(output).toContain('Received string: undefined'); + expect(output).toContain('Received string: ""'); + expect(output).toContain('waiting for selector "no-such-thing"'); +}); + +test('should print expected/received on Ctrl+C', async ({ runInlineTest }) => { + test.skip(process.platform === 'win32', 'No sending SIGINT on Windows'); + + const result = await runInlineTest({ + 'a.test.ts': ` + const { test } = pwt; + + test('times out waiting for text', async ({ page }) => { + await page.setContent('
Text content
'); + const promise = expect(page.locator('#node')).toHaveText('Text 2'); + await new Promise(f => setTimeout(f, 500)); + console.log('\\n%%SEND-SIGINT%%'); + await promise; + }); + `, + }, { workers: 1 }, {}, { sendSIGINTAfter: 1 }); + expect(result.exitCode).toBe(130); + expect(result.passed).toBe(0); + expect(result.skipped).toBe(1); + expect(stripAscii(result.output)).toContain('Expected string: "Text 2"'); + expect(stripAscii(result.output)).toContain('Received string: "Text content"'); +}); + +test('should support not.toHaveText when selector does not match', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.test.ts': ` + const { test } = pwt; + + test('fails', async ({ page }) => { + await page.setContent('
hello
'); + await expect(page.locator('span')).not.toHaveText('hello', { timeout: 1000 }); + }); + `, + }, { workers: 1 }); + expect(result.exitCode).toBe(1); + expect(result.passed).toBe(0); + expect(result.failed).toBe(1); + const output = stripAscii(result.output); + expect(output).toContain('Expected string: not "hello"'); + expect(output).toContain('Received string: ""'); + expect(output).toContain('waiting for selector "span"'); });