cherry-pick(#8990): fix(inspector): stop on all snapshottable actions
This commit is contained in:
parent
be1eb6887a
commit
46923b482e
|
|
@ -3666,6 +3666,7 @@ export const commandsWithTracingSnapshots = new Set([
|
|||
'Frame.addStyleTag',
|
||||
'Frame.check',
|
||||
'Frame.click',
|
||||
'Frame.dragAndDrop',
|
||||
'Frame.dblclick',
|
||||
'Frame.dispatchEvent',
|
||||
'Frame.evaluateExpression',
|
||||
|
|
@ -3681,6 +3682,8 @@ export const commandsWithTracingSnapshots = new Set([
|
|||
'Frame.isChecked',
|
||||
'Frame.isDisabled',
|
||||
'Frame.isEnabled',
|
||||
'Frame.isHidden',
|
||||
'Frame.isVisible',
|
||||
'Frame.isEditable',
|
||||
'Frame.press',
|
||||
'Frame.selectOption',
|
||||
|
|
@ -3704,14 +3707,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'
|
||||
]);
|
||||
|
|
@ -1272,6 +1272,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
click:
|
||||
parameters:
|
||||
|
|
@ -1301,6 +1302,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
content:
|
||||
returns:
|
||||
|
|
@ -1316,6 +1318,9 @@ Frame:
|
|||
trial: boolean?
|
||||
sourcePosition: Point?
|
||||
targetPosition: Point?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
dblclick:
|
||||
parameters:
|
||||
|
|
@ -1344,6 +1349,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
dispatchEvent:
|
||||
parameters:
|
||||
|
|
@ -1385,6 +1391,7 @@ Frame:
|
|||
noWaitAfter: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
focus:
|
||||
parameters:
|
||||
|
|
@ -1444,6 +1451,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
innerHTML:
|
||||
parameters:
|
||||
|
|
@ -1511,6 +1519,8 @@ Frame:
|
|||
strict: boolean?
|
||||
returns:
|
||||
value: boolean
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
isVisible:
|
||||
parameters:
|
||||
|
|
@ -1518,6 +1528,8 @@ Frame:
|
|||
strict: boolean?
|
||||
returns:
|
||||
value: boolean
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
isEditable:
|
||||
parameters:
|
||||
|
|
@ -1539,6 +1551,7 @@ Frame:
|
|||
timeout: number?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
querySelector:
|
||||
parameters:
|
||||
|
|
@ -1579,6 +1592,7 @@ Frame:
|
|||
items: string
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
setContent:
|
||||
parameters:
|
||||
|
|
@ -1609,6 +1623,7 @@ Frame:
|
|||
noWaitAfter: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
tap:
|
||||
parameters:
|
||||
|
|
@ -1630,6 +1645,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
textContent:
|
||||
parameters:
|
||||
|
|
@ -1655,6 +1671,7 @@ Frame:
|
|||
timeout: number?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
uncheck:
|
||||
parameters:
|
||||
|
|
@ -1667,6 +1684,7 @@ Frame:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
waitForFunction:
|
||||
parameters:
|
||||
|
|
@ -1857,6 +1875,7 @@ ElementHandle:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
click:
|
||||
parameters:
|
||||
|
|
@ -1884,6 +1903,7 @@ ElementHandle:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
contentFrame:
|
||||
returns:
|
||||
|
|
@ -1914,6 +1934,7 @@ ElementHandle:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
dispatchEvent:
|
||||
parameters:
|
||||
|
|
@ -1930,6 +1951,7 @@ ElementHandle:
|
|||
noWaitAfter: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
focus:
|
||||
|
||||
|
|
@ -1956,42 +1978,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:
|
||||
|
|
@ -2005,6 +2046,7 @@ ElementHandle:
|
|||
noWaitAfter: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
querySelector:
|
||||
parameters:
|
||||
|
|
@ -2062,6 +2104,7 @@ ElementHandle:
|
|||
items: string
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
selectText:
|
||||
parameters:
|
||||
|
|
@ -2084,6 +2127,7 @@ ElementHandle:
|
|||
noWaitAfter: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
tap:
|
||||
parameters:
|
||||
|
|
@ -2103,10 +2147,13 @@ ElementHandle:
|
|||
trial: boolean?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
textContent:
|
||||
returns:
|
||||
value: string?
|
||||
tracing:
|
||||
snapshot: true
|
||||
|
||||
type:
|
||||
parameters:
|
||||
|
|
@ -2116,6 +2163,7 @@ ElementHandle:
|
|||
timeout: number?
|
||||
tracing:
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
uncheck:
|
||||
parameters:
|
||||
|
|
@ -2126,6 +2174,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);
|
||||
|
|
@ -102,6 +102,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