-
Notifications
You must be signed in to change notification settings - Fork 4.3k
feat: --debug=end
#36153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: --debug=end
#36153
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Adds a new --debug=end
mode that pauses just before tearing down the last test run, letting developers inspect the environment interactively.
- Propagate an
isLastTest
flag from the scheduler into the worker, and use it to trigger a pause at test end - Implement pause/resume logic in
TestInfoImpl
, adjust test duration to exclude paused time - Expose a new
onTestPaused
reporter hook and update CLI parsing/documentation fordebug
modes
Reviewed Changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
File | Description |
---|---|
workerMain.ts | Pass full TestEntry (with isLastTest ) into _runTest and tweak duration calculation |
testInfo.ts | Add _maybeDebugAtEnd , _pause /_resume logic, track paused time |
failureTracker.ts | Introduce nextFailureReachesMaxFailures helper |
dispatcher.ts | Include isLastTest in runInWorker , dispatch testPaused events |
reporterV2.ts | Declare new optional onTestPaused method |
multiplexer.ts | Forward onTestPaused to all registered reporters |
internalReporter.ts | Forward onTestPaused to the internal reporter |
base.ts (TerminalReporter) | Implement onTestPaused to skip epilogue and show debug message |
program.ts | Disallow --debug=end in UI/watch modes; refine --debug option parsing |
index.ts (fixtures) | Hook page fixture into debug-end mode to pause/resume browser page |
common/ipc.ts | Update IPC types: debug is now `"begin" |
common/config.ts | Adjust default overrides when debug === 'begin' |
docs/src/test-reporter-api/class-reporter.md | Document the new Reporter.onTestPaused hook |
Comments suppressed due to low confidence (2)
packages/playwright/src/worker/testInfo.ts:76
- [nitpick] The field name
_retries
is ambiguous; consider renaming it to_maxRetries
to clarify that it represents the maximum retry count.
private readonly _retries: number;
docs/src/test-reporter-api/class-reporter.md:272
- [nitpick] Parameter bullets under
Reporter.onTestPaused
need consistent indentation (two spaces) for proper markdown rendering.
- `test` <[TestCase]>
@@ -306,7 +306,17 @@ class JobDispatcher { | |||
this._currentlyRunning = { test, result }; | |||
} | |||
|
|||
private _onTestEnd(params: TestEndPayload) { | |||
private _onTestPaused(params: TestEndPayload) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Both _onTestPaused
and _onTestEnd
call _updateTest
; consider extracting the shared update logic into a single helper to reduce duplication.
Copilot uses AI. Check for mistakes.
void this.onEnd(fakeEnd).then(() => { | ||
this._skipEpilogue = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting _skipEpilogue
inside the .then()
callback may introduce a race; consider assigning _skipEpilogue = true
before calling onEnd
to ensure the epilogue is always skipped.
void this.onEnd(fakeEnd).then(() => { | |
this._skipEpilogue = true; | |
this._skipEpilogue = true; | |
void this.onEnd(fakeEnd).then(() => { |
Copilot uses AI. Check for mistakes.
packages/playwright/src/program.ts
Outdated
@@ -173,6 +173,8 @@ async function runTests(args: string[], opts: { [key: string]: any }) { | |||
if (opts.ui || opts.uiHost || opts.uiPort) { | |||
if (opts.onlyChanged) | |||
throw new Error(`--only-changed is not supported in UI mode. If you'd like that to change, see https://github.com/microsoft/playwright/issues/15075 for more details.`); | |||
if (cliOverrides.debug === 'end') | |||
throw new Error(`--debug=end is not supported in UI mode. If you'd like that to change, file an issue and let us know about your usecase for it.`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in error message: "usecase" should be two words ("use case") for correct spelling.
throw new Error(`--debug=end is not supported in UI mode. If you'd like that to change, file an issue and let us know about your usecase for it.`); | |
throw new Error(`--debug=end is not supported in UI mode. If you'd like that to change, file an issue and let us know about your use case for it.`); |
Copilot uses AI. Check for mistakes.
This comment has been minimized.
This comment has been minimized.
process.env.PWDEBUG = '1'; | ||
if (!['begin', 'end'].includes(options.debug)) | ||
throw new Error(`Unsupported debug mode "${options.debug}", must be one of "begin" or "end"`); | ||
overrides.debug = options.debug; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I'm missing the place where --debug
without a value is converted to begin
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed!
@@ -413,6 +424,54 @@ export class TestInfoImpl implements TestInfo { | |||
this._timeoutManager.setIgnoreTimeouts(); | |||
} | |||
|
|||
async _pause() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather move all this code to WorkerMain
. It handles the test flow, while TestInfo
has utility functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'll try doing this as part of the onDidTestFunctionFinish
change.
@@ -473,7 +476,7 @@ export class WorkerMain extends ProcessRunner { | |||
await testInfo._tracing.stopIfNeeded(); | |||
}).catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors. | |||
|
|||
testInfo.duration = (testInfo._timeoutManager.defaultSlot().elapsed + afterHooksSlot.elapsed) | 0; | |||
testInfo.duration = (testInfo._timeoutManager.defaultSlot().elapsed - testInfo._pausedSlot.elapsed + afterHooksSlot.elapsed) | 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fancy, but I'm not sure anyone would care about the duration of a paused test 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It reads almost like pure maths ^^ So should we take it into account or not?
@@ -380,6 +381,8 @@ export class WorkerMain extends ProcessRunner { | |||
// No skips in after hooks. | |||
testInfo._allowSkips = true; | |||
|
|||
await testInfo._maybeDebugAtEnd(entry.isLastTest); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this is close to running testInfo._onDidFinishTestFunction
? Perhaps combine the two, or at least run them in the same place.
packages/playwright/src/index.ts
Outdated
|
||
const disposables: Disposable[] = []; | ||
if (testInfoImpl._configInternal.configCLIOverrides.debug === 'end') { | ||
testInfoImpl._onPaused(location => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, perhaps reusing "onTestFunctionFinished" callback will make things better here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. onTestFunctionFinished
is currently controlled by ArtifactRecorder
though, I'll see how big of a change this is to untangle.
|
||
import { TimeoutManager, TimeoutManagerError, kMaxDeadline } from './timeoutManager'; | ||
import { addSuffixToFilePath, filteredStackTrace, getContainedPath, normalizeAndSaveAttachment, sanitizeFilePathBeforeExtension, trimLongString, windowsFilesystemFriendlyLength } from '../util'; | ||
import { TestTracing } from './testTracing'; | ||
import { testInfoError } from './util'; | ||
import { EventEmitter } from '../isomorphic/events'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are in Node here, let's use Node's EventEmitter
.
Co-authored-by: Dmitry Gozman <dgozman@gmail.com> Signed-off-by: Simon Knott <info@simonknott.de>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the good feedback! I've addressed most. Gonna look at _onDidFinishTestFunction
now.
@@ -262,6 +262,22 @@ Test that has been started. | |||
|
|||
Result of the test run, this object gets populated while the test runs. | |||
|
|||
## optional method: Reporter.onTestPaused |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I've added a naïve implementation.
packages/playwright/src/index.ts
Outdated
|
||
const disposables: Disposable[] = []; | ||
if (testInfoImpl._configInternal.configCLIOverrides.debug === 'end') { | ||
testInfoImpl._onPaused(location => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. onTestFunctionFinished
is currently controlled by ArtifactRecorder
though, I'll see how big of a change this is to untangle.
process.env.PWDEBUG = '1'; | ||
if (!['begin', 'end'].includes(options.debug)) | ||
throw new Error(`Unsupported debug mode "${options.debug}", must be one of "begin" or "end"`); | ||
overrides.debug = options.debug; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed!
@@ -413,6 +424,54 @@ export class TestInfoImpl implements TestInfo { | |||
this._timeoutManager.setIgnoreTimeouts(); | |||
} | |||
|
|||
async _pause() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'll try doing this as part of the onDidTestFunctionFinish
change.
@@ -473,7 +476,7 @@ export class WorkerMain extends ProcessRunner { | |||
await testInfo._tracing.stopIfNeeded(); | |||
}).catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors. | |||
|
|||
testInfo.duration = (testInfo._timeoutManager.defaultSlot().elapsed + afterHooksSlot.elapsed) | 0; | |||
testInfo.duration = (testInfo._timeoutManager.defaultSlot().elapsed - testInfo._pausedSlot.elapsed + afterHooksSlot.elapsed) | 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It reads almost like pure maths ^^ So should we take it into account or not?
return { testId: test.id, retry: test.results.length }; | ||
entries: this.job.tests.map((test, index) => { | ||
let isLastTest: boolean | 'if-failure' = isEnd && index === this.job.tests.length - 1; | ||
if (!isLastTest && this._failureTracker.hasReachedMaxFailures()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove interaction with maxFailures
done
It seems like when running with --debug=end, we should always stop at the first test failure if that ever happens?
I don't think so. In CLI usage, I expect users to focus on a single test, so this doesn't matter. But when used in VS Code, it's important we run the same tests as without the flag, and always pause the last test. So we shouldn't stop based on first failure. Maybe VS Code needs a slightly different mode?
await this._pause(); | ||
} | ||
|
||
_willBeRunAgain() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the retry logic into Dispatcher
. I think we should take it into account so we don't change execution order for VS Code. CLI usage is probably different - starts feeling like two separate modes emerge.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I migrated to _onDidTestFunctionEnd
. That didn't excatly make the code cleaner because _onDidTestFunctionEnd
is only used in ArtifactsRecorder
. I added a second _onTestPaused
function that's similar though - much easier to follow.
Test results for "tests 1"10 flaky39274 passed, 818 skipped Merge workflow run. |
Adds a
--debug=end
mode that pauses a test run just before the teardown of the very last test, so that the developer can inspect the test environment. Useful for debugging a failed test, or for interactively writing a test that depends on resources managed by the test runner.There's a couple of missing pieces that I left out of this PR for focus:
The aspect of this PR that i'm most sceptical about is around
_maybeDebugAtEnd
. To decide if it should pause, the worker needs to know if there's another test coming after it. The worker doesn't know that though, this knowledge lives in the scheduler on the host. So I had to rebuild it. It feels brittle. The alternative would mean that the worker and scheduler need to synchronize though, and that feels like a higher level of complexity.🚨 don't merge before 1.53 branch point 🚨