feat: print response text when toBeOK fails (#16040)
This commit is contained in:
Родитель
4a47e275c8
Коммит
af8e3e7afa
|
@ -28,6 +28,7 @@
|
|||
"./lib/utils/httpServer": "./lib/utils/httpServer.js",
|
||||
"./lib/utils/hostPlatform": "./lib/utils/hostPlatform.js",
|
||||
"./lib/utils/manualPromise": "./lib/utils/manualPromise.js",
|
||||
"./lib/utils/mimeType": "./lib/utils/mimeType.js",
|
||||
"./lib/utils/multimap": "./lib/utils/multimap.js",
|
||||
"./lib/utils/processLauncher": "./lib/utils/processLauncher.js",
|
||||
"./lib/utils/processLauncherCleanupEntrypoint": "./lib/utils/processLauncherCleanupEntrypoint.js",
|
||||
|
|
|
@ -31,6 +31,7 @@ import { getPlaywrightVersion } from '../../common/userAgent';
|
|||
import { urlMatches } from '../../common/netUtils';
|
||||
import { Frame } from '../frames';
|
||||
import type { LifecycleEvent } from '../types';
|
||||
import { isTextualMimeType } from '../../utils/mimeType';
|
||||
|
||||
const FALLBACK_HTTP_VERSION = 'HTTP/1.1';
|
||||
|
||||
|
@ -599,7 +600,3 @@ function parseCookie(c: string): har.Cookie {
|
|||
}
|
||||
return cookie;
|
||||
}
|
||||
|
||||
function isTextualMimeType(mimeType: string) {
|
||||
return !!mimeType.match(/^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$/);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export function isTextualMimeType(mimeType: string) {
|
||||
return !!mimeType.match(/^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$/);
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import type { Locator, Page, APIResponse } from 'playwright-core';
|
||||
import type { FrameExpectOptions } from 'playwright-core/lib/client/types';
|
||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||
import { constructURLBasedOnBaseURL } from 'playwright-core/lib/utils';
|
||||
import type { Expect } from '../types';
|
||||
import { expectTypes, callLogText } from '../util';
|
||||
|
@ -23,6 +24,7 @@ import { toBeTruthy } from './toBeTruthy';
|
|||
import { toEqual } from './toEqual';
|
||||
import { toExpectedTextValues, toMatchText } from './toMatchText';
|
||||
import type { ParsedStackTrace } from 'playwright-core/lib/utils/stackTrace';
|
||||
import { isTextualMimeType } from 'playwright-core/lib/utils/mimeType';
|
||||
|
||||
interface LocatorEx extends Locator {
|
||||
_expect(customStackTrace: ParsedStackTrace, expression: string, options: Omit<FrameExpectOptions, 'expectedValue'> & { expectedValue?: any }): Promise<{ matches: boolean, received?: any, log?: string[] }>;
|
||||
|
@ -289,8 +291,18 @@ export async function toBeOK(
|
|||
) {
|
||||
const matcherName = 'toBeOK';
|
||||
expectTypes(response, ['APIResponse'], matcherName);
|
||||
const log = (this.isNot === response.ok()) ? await response._fetchLog() : [];
|
||||
const message = () => this.utils.matcherHint(matcherName, undefined, '', { isNot: this.isNot }) + callLogText(log);
|
||||
|
||||
const contentType = response.headers()['content-type'];
|
||||
const isTextEncoding = contentType && isTextualMimeType(contentType);
|
||||
const [log, text] = (this.isNot === response.ok()) ? await Promise.all([
|
||||
response._fetchLog(),
|
||||
isTextEncoding ? response.text() : null
|
||||
]) : [];
|
||||
|
||||
const message = () => this.utils.matcherHint(matcherName, undefined, '', { isNot: this.isNot }) +
|
||||
callLogText(log) +
|
||||
(text === null ? '' : `\nResponse text:\n${colors.dim(text?.substring(0, 1000) || '')}`);
|
||||
|
||||
const pass = response.ok();
|
||||
return { message, pass };
|
||||
}
|
||||
|
|
|
@ -417,3 +417,65 @@ test('should support toBeOK', async ({ runInlineTest, server }) => {
|
|||
expect(result.output).toContain(`← 404 Not Found`);
|
||||
expect(result.output).toContain(`Error: toBeOK can be only used with APIResponse object`);
|
||||
});
|
||||
|
||||
test('should print response text if toBeOK fails', async ({ runInlineTest, server }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('fail', async ({ page }) => {
|
||||
const res = await page.request.get('${server.PREFIX}/unknown');
|
||||
await expect(res).toBeOK();
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.failed).toBe(1);
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.output).toContain(`→ GET ${server.PREFIX}/unknown`);
|
||||
expect(result.output).toContain(`← 404 Not Found`);
|
||||
expect(result.output).toContain(`Response text:`);
|
||||
expect(result.output).toContain(`File not found`);
|
||||
});
|
||||
|
||||
test('should only print response with text content type if toBeOK fails', async ({ runInlineTest, server }) => {
|
||||
server.setRoute('/text-content-type', (req, res) => {
|
||||
res.statusCode = 404;
|
||||
res.setHeader('Content-type', 'text/plain');
|
||||
res.end('Text error');
|
||||
});
|
||||
server.setRoute('/no-content-type', (req, res) => {
|
||||
res.statusCode = 404;
|
||||
res.end('No content type error');
|
||||
});
|
||||
server.setRoute('/binary-content-type', (req, res) => {
|
||||
res.statusCode = 404;
|
||||
res.setHeader('Content-type', 'image/bmp');
|
||||
res.end('Image content type error');
|
||||
});
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test('text content type', async ({ page }) => {
|
||||
const res = await page.request.get('${server.PREFIX}/text-content-type');
|
||||
await expect(res).toBeOK();
|
||||
});
|
||||
|
||||
test('no content type', async ({ page }) => {
|
||||
const res = await page.request.get('${server.PREFIX}/no-content-type');
|
||||
await expect(res).toBeOK();
|
||||
});
|
||||
|
||||
test('image content type', async ({ page }) => {
|
||||
const res = await page.request.get('${server.PREFIX}/image-content-type');
|
||||
await expect(res).toBeOK();
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.failed).toBe(3);
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.output).toContain(`← 404 Not Found`);
|
||||
expect(result.output).toContain(`Text error`);
|
||||
expect(result.output).not.toContain(`No content type error`);
|
||||
expect(result.output).not.toContain(`Image content type error`);
|
||||
});
|
||||
|
|
|
@ -307,6 +307,7 @@ class TestServer {
|
|||
return;
|
||||
if (err) {
|
||||
response.statusCode = 404;
|
||||
response.setHeader('Content-Type', 'text/plain');
|
||||
response.end(`File not found: ${filePath}`);
|
||||
return;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче