mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix(expect): beautiful expect stacks (#9204)
We now mark our wrapper as `__PWTRAP__[expect.toHaveText]` and find it later in the stack trace. Added trace/inspector tests to ensure this behavior in the future.
This commit is contained in:
parent
b93718daea
commit
0a690778e4
@ -74,6 +74,10 @@ function wrap(matcherName: string, matcher: any) {
|
|||||||
return matcher.call(this, ...args);
|
return matcher.call(this, ...args);
|
||||||
|
|
||||||
const INTERNAL_STACK_LENGTH = 3;
|
const INTERNAL_STACK_LENGTH = 3;
|
||||||
|
// at Object.__PWTRAP__[expect.toHaveText] (...)
|
||||||
|
// at __EXTERNAL_MATCHER_TRAP__ (...)
|
||||||
|
// at Object.throwingMatcher [as toHaveText] (...)
|
||||||
|
// at <test function> (...)
|
||||||
const stackLines = new Error().stack!.split('\n').slice(INTERNAL_STACK_LENGTH + 1);
|
const stackLines = new Error().stack!.split('\n').slice(INTERNAL_STACK_LENGTH + 1);
|
||||||
const step = testInfo._addStep({
|
const step = testInfo._addStep({
|
||||||
category: 'expect',
|
category: 'expect',
|
||||||
@ -107,6 +111,7 @@ function wrap(matcherName: string, matcher: any) {
|
|||||||
reportStepError(e);
|
reportStepError(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
result.displayName = '__PWTRAP__[expect.' + matcherName + ']';
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,8 +33,6 @@ export function rewriteErrorMessage<E extends Error>(e: E, newMessage: string):
|
|||||||
const ROOT_DIR = path.resolve(__dirname, '..', '..');
|
const ROOT_DIR = path.resolve(__dirname, '..', '..');
|
||||||
const CLIENT_LIB = path.join(ROOT_DIR, 'lib', 'client');
|
const CLIENT_LIB = path.join(ROOT_DIR, 'lib', 'client');
|
||||||
const CLIENT_SRC = path.join(ROOT_DIR, 'src', 'client');
|
const CLIENT_SRC = path.join(ROOT_DIR, 'src', 'client');
|
||||||
const TEST_LIB = path.join(ROOT_DIR, 'lib', 'test');
|
|
||||||
const TEST_SRC = path.join(ROOT_DIR, 'src', 'test');
|
|
||||||
|
|
||||||
export type ParsedStackTrace = {
|
export type ParsedStackTrace = {
|
||||||
allFrames: StackFrame[];
|
allFrames: StackFrame[];
|
||||||
@ -65,14 +63,7 @@ export function captureStackTrace(): ParsedStackTrace {
|
|||||||
const fileName = path.resolve(process.cwd(), frame.file);
|
const fileName = path.resolve(process.cwd(), frame.file);
|
||||||
if (isTesting && fileName.includes(path.join('playwright', 'tests', 'config', 'coverage.js')))
|
if (isTesting && fileName.includes(path.join('playwright', 'tests', 'config', 'coverage.js')))
|
||||||
return null;
|
return null;
|
||||||
const inClient =
|
const inClient = fileName.startsWith(CLIENT_LIB) || fileName.startsWith(CLIENT_SRC);
|
||||||
// Allow fixtures in the reported stacks.
|
|
||||||
(!fileName.includes('test/index') && !fileName.includes('test\\index')) && (
|
|
||||||
frame.file.includes(path.join('node_modules', 'expect'))
|
|
||||||
|| fileName.startsWith(CLIENT_LIB)
|
|
||||||
|| fileName.startsWith(CLIENT_SRC)
|
|
||||||
|| fileName.startsWith(TEST_LIB)
|
|
||||||
|| fileName.startsWith(TEST_SRC));
|
|
||||||
const parsed: ParsedFrame = {
|
const parsed: ParsedFrame = {
|
||||||
frame: {
|
frame: {
|
||||||
file: fileName,
|
file: fileName,
|
||||||
@ -87,23 +78,29 @@ export function captureStackTrace(): ParsedStackTrace {
|
|||||||
}).filter(Boolean) as ParsedFrame[];
|
}).filter(Boolean) as ParsedFrame[];
|
||||||
|
|
||||||
let apiName = '';
|
let apiName = '';
|
||||||
// Deepest transition between non-client code calling into client code
|
|
||||||
// is the api entry.
|
|
||||||
const allFrames = parsedFrames;
|
const allFrames = parsedFrames;
|
||||||
for (let i = 0; i < parsedFrames.length - 1; i++) {
|
|
||||||
if (parsedFrames[i].inClient && !parsedFrames[i + 1].inClient) {
|
// expect matchers have the following stack structure:
|
||||||
const frame = parsedFrames[i].frame;
|
// at Object.__PWTRAP__[expect.toHaveText] (...)
|
||||||
const text = parsedFrames[i].frameText;
|
// at __EXTERNAL_MATCHER_TRAP__ (...)
|
||||||
// expect matchers have the following stack structure:
|
// at Object.throwingMatcher [as toHaveText] (...)
|
||||||
// at __EXTERNAL_MATCHER_TRAP__ (.../index.js:342:30)
|
const TRAP = '__PWTRAP__[';
|
||||||
// at Object.throwingMatcher [as toBeChecked] (.../index.js:343:15)
|
const expectIndex = parsedFrames.findIndex(f => f.frameText.includes(TRAP));
|
||||||
const aliasIndex = text.indexOf('[as ');
|
if (expectIndex !== -1) {
|
||||||
if (aliasIndex !== -1)
|
const text = parsedFrames[expectIndex].frameText;
|
||||||
apiName = 'expect.' + text.substring(aliasIndex + 4, text.indexOf(']'));
|
const aliasIndex = text.indexOf(TRAP);
|
||||||
else
|
apiName = text.substring(aliasIndex + TRAP.length, text.indexOf(']'));
|
||||||
|
parsedFrames = parsedFrames.slice(expectIndex + 3);
|
||||||
|
} else {
|
||||||
|
// Deepest transition between non-client code calling into client code
|
||||||
|
// is the api entry.
|
||||||
|
for (let i = 0; i < parsedFrames.length - 1; i++) {
|
||||||
|
if (parsedFrames[i].inClient && !parsedFrames[i + 1].inClient) {
|
||||||
|
const frame = parsedFrames[i].frame;
|
||||||
apiName = frame.function ? frame.function[0].toLowerCase() + frame.function.slice(1) : '';
|
apiName = frame.function ? frame.function[0].toLowerCase() + frame.function.slice(1) : '';
|
||||||
parsedFrames = parsedFrames.slice(i + 1);
|
parsedFrames = parsedFrames.slice(i + 1);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -173,6 +173,25 @@ it.describe('pause', () => {
|
|||||||
await scriptPromise;
|
await scriptPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show expect.toHaveText', async ({ page, recorderPageGetter }) => {
|
||||||
|
await page.setContent('<button>Submit</button>');
|
||||||
|
const scriptPromise = (async () => {
|
||||||
|
await page.pause();
|
||||||
|
await expect(page.locator('button')).toHaveText('Submit');
|
||||||
|
await page.pause(); // 2
|
||||||
|
})();
|
||||||
|
const recorderPage = await recorderPageGetter();
|
||||||
|
await recorderPage.click('[title="Resume"]');
|
||||||
|
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause(); // 2")');
|
||||||
|
expect(await sanitizeLog(recorderPage)).toEqual([
|
||||||
|
'page.pause- XXms',
|
||||||
|
'expect.toHaveText(button)- XXms',
|
||||||
|
'page.pause',
|
||||||
|
]);
|
||||||
|
await recorderPage.click('[title="Resume"]');
|
||||||
|
await scriptPromise;
|
||||||
|
});
|
||||||
|
|
||||||
it('should highlight waitForEvent', async ({ page, recorderPageGetter }) => {
|
it('should highlight waitForEvent', async ({ page, recorderPageGetter }) => {
|
||||||
await page.setContent('<button onclick="console.log(1)">Submit</button>');
|
await page.setContent('<button onclick="console.log(1)">Submit</button>');
|
||||||
const scriptPromise = (async () => {
|
const scriptPromise = (async () => {
|
||||||
|
|||||||
@ -105,6 +105,7 @@ test.beforeAll(async function recordTrace({ browser, browserName, browserType, s
|
|||||||
const page = await context.newPage();
|
const page = await context.newPage();
|
||||||
await page.goto('data:text/html,<html>Hello world</html>');
|
await page.goto('data:text/html,<html>Hello world</html>');
|
||||||
await page.setContent('<button>Click</button>');
|
await page.setContent('<button>Click</button>');
|
||||||
|
await expect(page.locator('button')).toHaveText('Click');
|
||||||
await page.evaluate(({ a }) => {
|
await page.evaluate(({ a }) => {
|
||||||
console.log('Info');
|
console.log('Info');
|
||||||
console.warn('Warning');
|
console.warn('Warning');
|
||||||
@ -158,6 +159,7 @@ test('should open simple trace viewer', async ({ showTraceViewer }) => {
|
|||||||
await expect(traceViewer.actionTitles).toHaveText([
|
await expect(traceViewer.actionTitles).toHaveText([
|
||||||
/page.gotodata:text\/html,<html>Hello world<\/html>— [\d.ms]+/,
|
/page.gotodata:text\/html,<html>Hello world<\/html>— [\d.ms]+/,
|
||||||
/page.setContent— [\d.ms]+/,
|
/page.setContent— [\d.ms]+/,
|
||||||
|
/expect.toHaveTextbutton— [\d.ms]+/,
|
||||||
/page.evaluate— [\d.ms]+/,
|
/page.evaluate— [\d.ms]+/,
|
||||||
/page.click"Click"— [\d.ms]+/,
|
/page.click"Click"— [\d.ms]+/,
|
||||||
/page.waitForEvent— [\d.ms]+/,
|
/page.waitForEvent— [\d.ms]+/,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user