fix(inspector): stop on all snapshottable actions (#8990)
This commit is contained in:
parent
e7a7a0cfc1
commit
63ff405e6e
|
|
@ -3668,6 +3668,7 @@ export const commandsWithTracingSnapshots = new Set([
|
|||
'Frame.addStyleTag',
|
||||
'Frame.check',
|
||||
'Frame.click',
|
||||
'Frame.dragAndDrop',
|
||||
'Frame.dblclick',
|
||||
'Frame.dispatchEvent',
|
||||
'Frame.evaluateExpression',
|
||||
|
|
@ -3683,6 +3684,8 @@ export const commandsWithTracingSnapshots = new Set([
|
|||
'Frame.isChecked',
|
||||
'Frame.isDisabled',
|
||||
'Frame.isEnabled',
|
||||
'Frame.isHidden',
|
||||
'Frame.isVisible',
|
||||
'Frame.isEditable',
|
||||
'Frame.press',
|
||||
'Frame.selectOption',
|
||||
|
|
@ -3706,14 +3709,50 @@ export const commandsWithTracingSnapshots = new Set([
|
|||
'ElementHandle.dispatchEvent',
|
||||
'ElementHandle.fill',
|
||||
'ElementHandle.hover',
|
||||
'ElementHandle.innerHTML',
|
||||
'ElementHandle.innerText',
|
||||
'ElementHandle.inputValue',
|
||||
'ElementHandle.isChecked',
|
||||
'ElementHandle.isDisabled',
|
||||
'ElementHandle.isEditable',
|
||||
'ElementHandle.isEnabled',
|
||||
'ElementHandle.isHidden',
|
||||
'ElementHandle.isVisible',
|
||||
'ElementHandle.press',
|
||||
'ElementHandle.scrollIntoViewIfNeeded',
|
||||
'ElementHandle.selectOption',
|
||||
'ElementHandle.selectText',
|
||||
'ElementHandle.setInputFiles',
|
||||
'ElementHandle.tap',
|
||||
'ElementHandle.textContent',
|
||||
'ElementHandle.type',
|
||||
'ElementHandle.uncheck',
|
||||
'ElementHandle.waitForElementState',
|
||||
'ElementHandle.waitForSelector'
|
||||
]);
|
||||
|
||||
export const pausesBeforeInputActions = new Set([
|
||||
'Frame.check',
|
||||
'Frame.click',
|
||||
'Frame.dragAndDrop',
|
||||
'Frame.dblclick',
|
||||
'Frame.fill',
|
||||
'Frame.hover',
|
||||
'Frame.press',
|
||||
'Frame.selectOption',
|
||||
'Frame.setInputFiles',
|
||||
'Frame.tap',
|
||||
'Frame.type',
|
||||
'Frame.uncheck',
|
||||
'ElementHandle.check',
|
||||
'ElementHandle.click',
|
||||
'ElementHandle.dblclick',
|
||||
'ElementHandle.fill',
|
||||
'ElementHandle.hover',
|
||||
'ElementHandle.press',
|
||||
'ElementHandle.selectOption',
|
||||
'ElementHandle.setInputFiles',
|
||||
'ElementHandle.tap',
|
||||
'ElementHandle.type',
|
||||
'ElementHandle.uncheck'
|
||||
]);
|
||||
|
|
@ -1273,6 +1273,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
click:
|
||||
parameters:
|
||||
|
|
@ -1302,6 +1303,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
content:
|
||||
returns:
|
||||
|
|
@ -1317,6 +1319,9 @@ Frame:
|
|||
trial: boolean?
|
||||
sourcePosition: Point?
|
||||
targetPosition: Point?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
dblclick:
|
||||
parameters:
|
||||
|
|
@ -1345,6 +1350,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
dispatchEvent:
|
||||
parameters:
|
||||
|
|
@ -1386,6 +1392,7 @@ Frame:
|
|||
noWaitAfter: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
focus:
|
||||
parameters:
|
||||
|
|
@ -1445,6 +1452,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
innerHTML:
|
||||
parameters:
|
||||
|
|
@ -1512,6 +1520,8 @@ Frame:
|
|||
strict: boolean?
|
||||
returns:
|
||||
value: boolean
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
isVisible:
|
||||
parameters:
|
||||
|
|
@ -1519,6 +1529,8 @@ Frame:
|
|||
strict: boolean?
|
||||
returns:
|
||||
value: boolean
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
isEditable:
|
||||
parameters:
|
||||
|
|
@ -1540,6 +1552,7 @@ Frame:
|
|||
timeout: number?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
querySelector:
|
||||
parameters:
|
||||
|
|
@ -1580,6 +1593,7 @@ Frame:
|
|||
items: string
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
setContent:
|
||||
parameters:
|
||||
|
|
@ -1610,6 +1624,7 @@ Frame:
|
|||
noWaitAfter: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
tap:
|
||||
parameters:
|
||||
|
|
@ -1631,6 +1646,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
textContent:
|
||||
parameters:
|
||||
|
|
@ -1656,6 +1672,7 @@ Frame:
|
|||
timeout: number?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
uncheck:
|
||||
parameters:
|
||||
|
|
@ -1668,6 +1685,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
waitForFunction:
|
||||
parameters:
|
||||
|
|
@ -1858,6 +1876,7 @@ ElementHandle:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
click:
|
||||
parameters:
|
||||
|
|
@ -1885,6 +1904,7 @@ ElementHandle:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
contentFrame:
|
||||
returns:
|
||||
|
|
@ -1915,6 +1935,7 @@ ElementHandle:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
dispatchEvent:
|
||||
parameters:
|
||||
|
|
@ -1931,6 +1952,7 @@ ElementHandle:
|
|||
noWaitAfter: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
focus:
|
||||
|
||||
|
|
@ -1957,42 +1979,61 @@ ElementHandle:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
innerHTML:
|
||||
returns:
|
||||
value: string
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
innerText:
|
||||
returns:
|
||||
value: string
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
inputValue:
|
||||
returns:
|
||||
value: string
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
isChecked:
|
||||
returns:
|
||||
value: boolean
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
isDisabled:
|
||||
returns:
|
||||
value: boolean
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
isEditable:
|
||||
returns:
|
||||
value: boolean
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
isEnabled:
|
||||
returns:
|
||||
value: boolean
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
isHidden:
|
||||
returns:
|
||||
value: boolean
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
isVisible:
|
||||
returns:
|
||||
value: boolean
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
ownerFrame:
|
||||
returns:
|
||||
|
|
@ -2006,6 +2047,7 @@ ElementHandle:
|
|||
noWaitAfter: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
querySelector:
|
||||
parameters:
|
||||
|
|
@ -2063,6 +2105,7 @@ ElementHandle:
|
|||
items: string
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
selectText:
|
||||
parameters:
|
||||
|
|
@ -2085,6 +2128,7 @@ ElementHandle:
|
|||
noWaitAfter: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
tap:
|
||||
parameters:
|
||||
|
|
@ -2104,10 +2148,13 @@ ElementHandle:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
textContent:
|
||||
returns:
|
||||
value: string?
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
type:
|
||||
parameters:
|
||||
|
|
@ -2117,6 +2164,7 @@ ElementHandle:
|
|||
timeout: number?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
uncheck:
|
||||
parameters:
|
||||
|
|
@ -2127,6 +2175,7 @@ ElementHandle:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
waitForElementState:
|
||||
parameters:
|
||||
|
|
|
|||
|
|
@ -989,6 +989,7 @@ export function textContentTask(selector: SelectorInfo): SchedulableTask<string
|
|||
if (!element)
|
||||
return continuePolling;
|
||||
progress.log(` selector resolved to ${injected.previewNode(element)}`);
|
||||
progress.log(` retrieving textContent`);
|
||||
return element.textContent;
|
||||
});
|
||||
}, { parsed: selector.parsed, strict: selector.strict });
|
||||
|
|
|
|||
|
|
@ -1040,7 +1040,7 @@ export class Frame extends SdkObject {
|
|||
const info = this._page.parseSelector(selector, options);
|
||||
const task = dom.textContentTask(info);
|
||||
return controller.run(async progress => {
|
||||
progress.log(` retrieving textContent from "${selector}"`);
|
||||
progress.log(` waiting for selector "${selector}"\u2026`);
|
||||
return this._scheduleRerunnableTask(progress, info.world, task);
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { debugMode, isUnderTest, monotonicTime } from '../../utils/utils';
|
|||
import { BrowserContext } from '../browserContext';
|
||||
import { CallMetadata, InstrumentationListener, SdkObject } from '../instrumentation';
|
||||
import { debugLogger } from '../../utils/debugLogger';
|
||||
import { commandsWithTracingSnapshots, pausesBeforeInputActions } from '../../protocol/channels';
|
||||
|
||||
const symbol = Symbol('Debugger');
|
||||
|
||||
|
|
@ -55,7 +56,7 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
|
|||
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||
if (this._muted)
|
||||
return;
|
||||
if (shouldPauseOnCall(sdkObject, metadata) || (this._pauseOnNextStatement && shouldPauseOnNonInputStep(sdkObject, metadata)))
|
||||
if (shouldPauseOnCall(sdkObject, metadata) || (this._pauseOnNextStatement && shouldPauseBeforeStep(metadata)))
|
||||
await this.pause(sdkObject, metadata);
|
||||
}
|
||||
|
||||
|
|
@ -117,8 +118,14 @@ function shouldPauseOnCall(sdkObject: SdkObject, metadata: CallMetadata): boolea
|
|||
return metadata.method === 'pause';
|
||||
}
|
||||
|
||||
const nonInputActionsToStep = new Set(['close', 'evaluate', 'evaluateHandle', 'goto', 'setContent']);
|
||||
|
||||
function shouldPauseOnNonInputStep(sdkObject: SdkObject, metadata: CallMetadata): boolean {
|
||||
return nonInputActionsToStep.has(metadata.method);
|
||||
function shouldPauseBeforeStep(metadata: CallMetadata): boolean {
|
||||
// Always stop on 'close'
|
||||
if (metadata.method === 'close')
|
||||
return true;
|
||||
if (metadata.method === 'waitForSelector' || metadata.method === 'waitForEventInfo')
|
||||
return false; // Never stop on those, primarily for the test harness.
|
||||
const step = metadata.type + '.' + metadata.method;
|
||||
// Stop before everything that generates snapshot. But don't stop before those marked as pausesBeforeInputActions
|
||||
// since we stop in them on a separate instrumentation signal.
|
||||
return commandsWithTracingSnapshots.has(step) && !pausesBeforeInputActions.has(metadata.type + '.' + metadata.method);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus)
|
|||
let title = metadata.apiName || metadata.method;
|
||||
if (metadata.method === 'waitForEventInfo')
|
||||
title += `(${metadata.params.info.event})`;
|
||||
title = title.replace('object.expect', 'expect');
|
||||
if (metadata.error)
|
||||
status = 'error';
|
||||
const params = {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ const customMatchers = {
|
|||
};
|
||||
|
||||
function wrap(matcherName: string, matcher: any) {
|
||||
return function(this: any, ...args: any[]) {
|
||||
const result = function(this: any, ...args: any[]) {
|
||||
const testInfo = currentTestInfo();
|
||||
if (!testInfo)
|
||||
return matcher.call(this, ...args);
|
||||
|
|
@ -107,6 +107,8 @@ function wrap(matcherName: string, matcher: any) {
|
|||
reportStepError(e);
|
||||
}
|
||||
};
|
||||
result.displayName = 'expect.' + matcherName;
|
||||
return result;
|
||||
}
|
||||
|
||||
const wrappedMatchers: any = {};
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ export function rewriteErrorMessage<E extends Error>(e: E, newMessage: string):
|
|||
const ROOT_DIR = path.resolve(__dirname, '..', '..');
|
||||
const CLIENT_LIB = path.join(ROOT_DIR, 'lib', '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 = {
|
||||
allFrames: StackFrame[];
|
||||
|
|
@ -60,9 +62,18 @@ export function captureStackTrace(): ParsedStackTrace {
|
|||
return null;
|
||||
if (frame.file.startsWith('internal'))
|
||||
return null;
|
||||
if (frame.file.includes(path.join('node_modules', 'expect')))
|
||||
return null;
|
||||
const fileName = path.resolve(process.cwd(), frame.file);
|
||||
if (isTesting && fileName.includes(path.join('playwright', 'tests', 'config', 'coverage.js')))
|
||||
return null;
|
||||
const inClient =
|
||||
// Allow fixtures in the reported stacks.
|
||||
(!fileName.includes('test/index') && !fileName.includes('test\\index')) && (
|
||||
fileName.startsWith(CLIENT_LIB)
|
||||
|| fileName.startsWith(CLIENT_SRC)
|
||||
|| fileName.startsWith(TEST_LIB)
|
||||
|| fileName.startsWith(TEST_SRC));
|
||||
const parsed: ParsedFrame = {
|
||||
frame: {
|
||||
file: fileName,
|
||||
|
|
@ -71,10 +82,10 @@ export function captureStackTrace(): ParsedStackTrace {
|
|||
function: frame.function,
|
||||
},
|
||||
frameText: line,
|
||||
inClient: fileName.startsWith(CLIENT_LIB) || fileName.startsWith(CLIENT_SRC),
|
||||
inClient
|
||||
};
|
||||
return parsed;
|
||||
}).filter(frame => !!frame) as ParsedFrame[];
|
||||
}).filter(Boolean) as ParsedFrame[];
|
||||
|
||||
let apiName = '';
|
||||
// Deepest transition between non-client code calling into client code
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
|
||||
.call-log-selector {
|
||||
color: var(--orange);
|
||||
white-space: normal;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.call-log-time {
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ test('should report error and pending operations on timeout', async ({ runInline
|
|||
expect(result.output).toContain('Pending operations:');
|
||||
expect(result.output).toContain('- page.click at a.test.ts:9:16');
|
||||
expect(result.output).toContain('- page.textContent at a.test.ts:10:16');
|
||||
expect(result.output).toContain('retrieving textContent from "text=More missing"');
|
||||
expect(result.output).toContain('waiting for selector');
|
||||
expect(stripAscii(result.output)).toContain(`10 | page.textContent('text=More missing'),`);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -229,9 +229,9 @@ test('should report expect steps', async ({ runInlineTest }) => {
|
|||
`%% end {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`,
|
||||
`%% begin {\"title\":\"expect.not.toHaveTitle\",\"category\":\"expect\"}`,
|
||||
`%% begin {\"title\":\"page.title\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"page.title\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"expect.not.toHaveTitle\",\"category\":\"expect\",\"steps\":[{\"title\":\"page.title\",\"category\":\"pw:api\"}]}`,
|
||||
`%% begin {\"title\":\"object.expect.toHaveTitle\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"object.expect.toHaveTitle\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"expect.not.toHaveTitle\",\"category\":\"expect\",\"steps\":[{\"title\":\"object.expect.toHaveTitle\",\"category\":\"pw:api\"}]}`,
|
||||
`%% begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%% begin {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||
`];
|
||||
|
||||
const tracingSnapshots = [];
|
||||
const pausesBeforeInputActions = [];
|
||||
|
||||
const yml = fs.readFileSync(path.join(__dirname, '..', 'src', 'protocol', 'protocol.yml'), 'utf-8');
|
||||
const protocol = yaml.parse(yml);
|
||||
|
|
@ -232,6 +233,11 @@ for (const [name, item] of Object.entries(protocol)) {
|
|||
for (const derived of derivedClasses.get(name) || [])
|
||||
tracingSnapshots.push(derived + '.' + methodName);
|
||||
}
|
||||
if (method.tracing && method.tracing.pausesBeforeInput) {
|
||||
pausesBeforeInputActions.push(name + '.' + methodName);
|
||||
for (const derived of derivedClasses.get(name) || [])
|
||||
pausesBeforeInputActions.push(derived + '.' + methodName);
|
||||
}
|
||||
const parameters = objectType(method.parameters || {}, '');
|
||||
const paramsName = `${channelName}${titleCase(methodName)}Params`;
|
||||
const optionsName = `${channelName}${titleCase(methodName)}Options`;
|
||||
|
|
@ -271,6 +277,10 @@ for (const [name, item] of Object.entries(protocol)) {
|
|||
channels_ts.push(`export const commandsWithTracingSnapshots = new Set([
|
||||
'${tracingSnapshots.join(`',\n '`)}'
|
||||
]);`);
|
||||
channels_ts.push('');
|
||||
channels_ts.push(`export const pausesBeforeInputActions = new Set([
|
||||
'${pausesBeforeInputActions.join(`',\n '`)}'
|
||||
]);`);
|
||||
|
||||
validator_ts.push(`
|
||||
return scheme;
|
||||
|
|
|
|||
Loading…
Reference in a new issue