[{"data":1,"prerenderedAt":951},["ShallowReactive",2],{"page-\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002Fdropping-into-pdb-on-test-failure\u002F":3},{"id":4,"title":5,"body":6,"description":918,"extension":919,"meta":920,"navigation":106,"path":947,"seo":948,"stem":949,"__hash__":950},"content\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002Fdropping-into-pdb-on-test-failure\u002Findex.md","Dropping into pdb on Test Failure with --pdb",{"type":7,"value":8,"toc":907},"minimark",[9,22,27,51,55,66,168,176,183,192,199,208,338,342,363,463,467,478,497,527,530,540,544,547,557,566,580,587,591,647,651,654,667,682,688,703,715,732,736,746,755,790,865,869,897,903],[10,11,12,13,17,18,21],"p",{},"A test fails with ",[14,15,16],"code",{},"assert result == expected"," and a diff of two large structures. Reproducing it in a REPL means recreating the fixtures by hand, which takes ten minutes and often produces a different failure. ",[14,19,20],{},"pytest --pdb"," skips all of that: it opens a debugger at the failing frame, with the fixtures already built and every local still bound.",[23,24,26],"h2",{"id":25},"prerequisites","Prerequisites",[28,29,30,42],"ul",{},[31,32,33,37,38,41],"li",{},[34,35,36],"strong",{},"pytest 7.0+",", and optionally ",[34,39,40],{},"ipdb"," for the richer prompt.",[31,43,44,45,50],{},"The command vocabulary from ",[46,47,49],"a",{"href":48},"\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002F","interactive debugging with pdb and ipdb",".",[23,52,54],{"id":53},"solution","Solution",[10,56,57,58,61,62,65],{},"Combine ",[14,59,60],{},"--pdb"," with ",[14,63,64],{},"-x"," so the session opens once, at the first failure:",[67,68,73],"pre",{"className":69,"code":70,"language":71,"meta":72,"style":72},"language-console shiki shiki-themes github-light github-dark","$ pytest tests\u002Ftest_orders.py --pdb -x\n______________________________ test_total ______________________________\n>       assert order.total() == 300\nE       assert 250 == 300\n\ntests\u002Ftest_orders.py:41: AssertionError\n>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>\n> \u002Fsrv\u002Fapp\u002Ftests\u002Ftest_orders.py(41)test_total()\n-> assert order.total() == 300\n(Pdb) p order.lines\n[Line(sku='A', qty=2, price=100), Line(sku='B', qty=1, price=50)]\n(Pdb) p [l.qty * l.price for l in order.lines]\n[200, 50]\n(Pdb) u                      # move up to the fixture frame, if needed\n(Pdb) c                      # continue: the run ends, teardown proceeds\n","console","",[14,74,75,83,89,95,101,108,114,120,126,132,138,144,150,156,162],{"__ignoreMap":72},[76,77,80],"span",{"class":78,"line":79},"line",1,[76,81,82],{},"$ pytest tests\u002Ftest_orders.py --pdb -x\n",[76,84,86],{"class":78,"line":85},2,[76,87,88],{},"______________________________ test_total ______________________________\n",[76,90,92],{"class":78,"line":91},3,[76,93,94],{},">       assert order.total() == 300\n",[76,96,98],{"class":78,"line":97},4,[76,99,100],{},"E       assert 250 == 300\n",[76,102,104],{"class":78,"line":103},5,[76,105,107],{"emptyLinePlaceholder":106},true,"\n",[76,109,111],{"class":78,"line":110},6,[76,112,113],{},"tests\u002Ftest_orders.py:41: AssertionError\n",[76,115,117],{"class":78,"line":116},7,[76,118,119],{},">>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>\n",[76,121,123],{"class":78,"line":122},8,[76,124,125],{},"> \u002Fsrv\u002Fapp\u002Ftests\u002Ftest_orders.py(41)test_total()\n",[76,127,129],{"class":78,"line":128},9,[76,130,131],{},"-> assert order.total() == 300\n",[76,133,135],{"class":78,"line":134},10,[76,136,137],{},"(Pdb) p order.lines\n",[76,139,141],{"class":78,"line":140},11,[76,142,143],{},"[Line(sku='A', qty=2, price=100), Line(sku='B', qty=1, price=50)]\n",[76,145,147],{"class":78,"line":146},12,[76,148,149],{},"(Pdb) p [l.qty * l.price for l in order.lines]\n",[76,151,153],{"class":78,"line":152},13,[76,154,155],{},"[200, 50]\n",[76,157,159],{"class":78,"line":158},14,[76,160,161],{},"(Pdb) u                      # move up to the fixture frame, if needed\n",[76,163,165],{"class":78,"line":164},15,[76,166,167],{},"(Pdb) c                      # continue: the run ends, teardown proceeds\n",[10,169,170,171,175],{},"The prompt opens ",[172,173,174],"em",{},"before"," fixture teardown, which is the property that makes it useful: the database rows, the temporary files and the started services are all still there to inspect.",[10,177,178,179,182],{},"Two variants cover the rest of the cases. ",[14,180,181],{},"--trace"," opens the debugger at the first line of every selected test, which is the right tool when the failure is a wrong value rather than an exception and you want to step forward from the start:",[67,184,186],{"className":69,"code":185,"language":71,"meta":72,"style":72},"$ pytest tests\u002Ftest_orders.py::test_total --trace\n",[14,187,188],{"__ignoreMap":72},[76,189,190],{"class":78,"line":79},[76,191,185],{},[10,193,194,195,198],{},"And ",[14,196,197],{},"--pdbcls"," swaps the debugger implementation, so the session gets syntax highlighting, tab completion and better tracebacks:",[67,200,202],{"className":69,"code":201,"language":71,"meta":72,"style":72},"$ pytest --pdb --pdbcls=IPython.terminal.debugger:TerminalPdb -x\n",[14,203,204],{"__ignoreMap":72},[76,205,206],{"class":78,"line":79},[76,207,201],{},[209,210,213,334],"figure",{"className":211},[212],"diagram",[214,215,222,223,222,227,222,231,222,249,222,257,222,266,222,275,222,280,222,285,222,291,222,294,222,298,222,301,222,305,222,308,222,311,222,314,222,318,222,321,222,325,222,328],"svg",{"viewBox":216,"role":217,"ariaLabelledBy":218,"xmlns":221},"0 0 760 172","img",[219,220],"pdbentry-t","pdbentry-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[224,225,226],"title",{"id":219},"Where each entry point opens the prompt",[228,229,230],"desc",{"id":220},"A left-to-right timeline of a test with four entry points marked: trace opens at the first line, a breakpoint call opens wherever it is placed, pdb opens post-mortem at the failure, and normal teardown runs after the session ends.",[232,233,234,235,222],"defs",{},"\n    ",[236,237,244],"marker",{"id":238,"viewBox":239,"refX":240,"refY":241,"markerWidth":242,"markerHeight":242,"orient":243},"pdbentry-a","0 0 10 10","9","5","7","auto-start-reverse",[245,246],"path",{"d":247,"fill":248},"M0 0 L10 5 L0 10 z","#e07a5f",[250,251],"rect",{"x":252,"y":252,"width":253,"height":254,"rx":255,"fill":256},"0","760","172","14","#fffdf8",[258,259,226],"text",{"x":260,"y":261,"textAnchor":262,"fontSize":263,"fontWeight":264,"fill":265},"380","30","middle","15","700","#3d405b",[250,267],{"x":268,"y":269,"width":270,"height":271,"rx":272,"fill":273,"stroke":248,"strokeWidth":274},"26","62","142","76","12","#f4f1de","1.8",[258,276,181],{"x":277,"y":278,"textAnchor":262,"fontSize":279,"fontWeight":264,"fill":265},"97","96","12.5",[258,281,284],{"x":277,"y":282,"textAnchor":262,"fontSize":283,"fill":265},"113","11","first line of the test",[78,286],{"x1":287,"y1":288,"x2":289,"y2":288,"stroke":248,"strokeWidth":274,"markerEnd":290},"176","100","208","url(#pdbentry-a)",[250,292],{"x":293,"y":269,"width":270,"height":271,"rx":272,"fill":256,"stroke":248,"strokeWidth":274},"214",[258,295,297],{"x":296,"y":278,"textAnchor":262,"fontSize":279,"fontWeight":264,"fill":265},"286","breakpoint()",[258,299,300],{"x":296,"y":282,"textAnchor":262,"fontSize":283,"fill":265},"wherever you put it",[78,302],{"x1":303,"y1":288,"x2":304,"y2":288,"stroke":248,"strokeWidth":274,"markerEnd":290},"364","396",[250,306],{"x":307,"y":269,"width":270,"height":271,"rx":272,"fill":273,"stroke":248,"strokeWidth":274},"403",[258,309,60],{"x":310,"y":278,"textAnchor":262,"fontSize":279,"fontWeight":264,"fill":265},"474",[258,312,313],{"x":310,"y":282,"textAnchor":262,"fontSize":283,"fill":265},"at the failure",[78,315],{"x1":316,"y1":288,"x2":317,"y2":288,"stroke":248,"strokeWidth":274,"markerEnd":290},"552","584",[250,319],{"x":320,"y":269,"width":270,"height":271,"rx":272,"fill":256,"stroke":248,"strokeWidth":274},"592",[258,322,324],{"x":323,"y":278,"textAnchor":262,"fontSize":279,"fontWeight":264,"fill":265},"663","teardown",[258,326,327],{"x":323,"y":282,"textAnchor":262,"fontSize":283,"fill":265},"after you continue",[258,329,333],{"x":260,"y":330,"textAnchor":262,"fontSize":331,"fontStyle":332,"fill":265},"164","11.5","italic","Continuing from a --pdb prompt resumes normal reporting and teardown.",[335,336,337],"figcaption",{},"All three prompts open before fixture teardown, so the environment the test built is still intact.",[23,339,341],{"id":340},"why-this-works","Why this works",[10,343,344,345,347,348,351,352,355,356,358,359,362],{},"When a test raises, pytest catches the exception to build its report. With ",[14,346,60],{}," it first calls ",[14,349,350],{},"pdb.post_mortem"," on the exception's traceback, which reconstructs the frame stack and opens a prompt at the innermost frame. Because the exception was caught rather than propagated, none of the ",[14,353,354],{},"finally"," blocks in the fixture teardown chain have run yet — the stack is intact and so is everything the fixtures created. ",[14,357,181],{}," uses a different mechanism, calling ",[14,360,361],{},"pdb.set_trace"," before the test function is invoked, which is why it steps forward rather than looking backwards.",[209,364,366,460],{"className":365},[212],[214,367,222,372,222,375,222,378,222,381,222,383,222,392,222,396,222,400,222,404,222,408,222,411,222,414,222,417,222,420,222,423,222,426,222,429,222,431,222,435,222,438,222,441,222,444,222,447,222,450,222,453,222,457],{"viewBox":368,"role":217,"ariaLabelledBy":369,"xmlns":221},"0 0 760 270",[370,371],"pdbflags-t","pdbflags-d",[224,373,374],{"id":370},"The pytest debugger flags",[228,376,377],{"id":371},"A table of four pytest debugging flags - pdb, trace, pdbcls and x - describing what each does and when it is the right choice.",[250,379],{"x":252,"y":252,"width":253,"height":380,"rx":255,"fill":256},"270",[258,382,374],{"x":260,"y":261,"textAnchor":262,"fontSize":263,"fontWeight":264,"fill":265},[250,384],{"x":385,"y":386,"width":387,"height":388,"rx":389,"fill":390,"stroke":265,"strokeWidth":391},"24","52","712","40","10","#f2cc8f","1.5",[258,393,395],{"x":394,"y":271,"fontSize":272,"fontWeight":264,"fill":265},"38","Criterion",[258,397,399],{"x":398,"y":271,"textAnchor":262,"fontSize":272,"fontWeight":264,"fill":265},"390","Does",[258,401,403],{"x":402,"y":271,"textAnchor":262,"fontSize":272,"fontWeight":264,"fill":265},"620","Use when",[250,405],{"x":385,"y":406,"width":387,"height":388,"fill":273,"stroke":265,"strokeWidth":407},"92","1",[258,409,60],{"x":394,"y":410,"fontSize":331,"fill":265},"116",[258,412,413],{"x":398,"y":410,"textAnchor":262,"fontSize":331,"fill":265},"post-mortem at the failure",[258,415,416],{"x":402,"y":410,"textAnchor":262,"fontSize":331,"fill":265},"an exception or assertion",[250,418],{"x":385,"y":419,"width":387,"height":388,"fill":256,"stroke":265,"strokeWidth":407},"132",[258,421,181],{"x":394,"y":422,"fontSize":331,"fill":265},"156",[258,424,425],{"x":398,"y":422,"textAnchor":262,"fontSize":331,"fill":265},"breaks at the first line",[258,427,428],{"x":402,"y":422,"textAnchor":262,"fontSize":331,"fill":265},"a wrong value, no exception",[250,430],{"x":385,"y":254,"width":387,"height":388,"fill":273,"stroke":265,"strokeWidth":407},[258,432,434],{"x":394,"y":433,"fontSize":331,"fill":265},"196","--pdbcls=mod:Class",[258,436,437],{"x":398,"y":433,"textAnchor":262,"fontSize":331,"fill":265},"swaps the debugger",[258,439,440],{"x":402,"y":433,"textAnchor":262,"fontSize":331,"fill":265},"you want ipdb or pudb",[250,442],{"x":385,"y":443,"width":387,"height":388,"fill":256,"stroke":265,"strokeWidth":407},"212",[258,445,64],{"x":394,"y":446,"fontSize":331,"fill":265},"236",[258,448,449],{"x":398,"y":446,"textAnchor":262,"fontSize":331,"fill":265},"stops at the first failure",[258,451,452],{"x":402,"y":446,"textAnchor":262,"fontSize":331,"fill":265},"always, with --pdb",[78,454],{"x1":455,"y1":386,"x2":455,"y2":456,"stroke":265,"strokeWidth":407},"274","252",[78,458],{"x1":459,"y1":386,"x2":459,"y2":456,"stroke":265,"strokeWidth":407},"505",[335,461,462],{},"The last row is the one people omit, and it is what keeps a debugging session from becoming fifty consecutive prompts.",[23,464,466],{"id":465},"what-the-prompt-can-and-cannot-see","What the prompt can and cannot see",[10,468,469,470,473,474,477],{},"The frame is real, so everything normally in scope is available: the test's locals, its arguments including fixture values, and every frame above it via ",[14,471,472],{},"u"," and ",[14,475,476],{},"d",". That makes a few commands especially productive.",[10,479,480,473,482,485,486,489,490,492,493,496],{},[14,481,10],{},[14,483,484],{},"pp"," print and pretty-print, and ",[14,487,488],{},"pp vars()"," dumps the whole local namespace at once — usually the fastest first move. ",[14,491,472],{}," walks up into the calling frame, which for a fixture-provided object means the fixture's own frame with its intermediate variables still bound. And ",[14,494,495],{},"!"," prefixes an arbitrary statement, so state can be modified and the hypothesis retested inside the session:",[67,498,500],{"className":69,"code":499,"language":71,"meta":72,"style":72},"(Pdb) pp vars()\n{'order': \u003COrder lines=2>, 'expected': 300, 'db_session': \u003CSession ...>}\n(Pdb) !order.lines[1].qty = 2        # try the hypothesis directly\n(Pdb) p order.total()\n300\n",[14,501,502,507,512,517,522],{"__ignoreMap":72},[76,503,504],{"class":78,"line":79},[76,505,506],{},"(Pdb) pp vars()\n",[76,508,509],{"class":78,"line":85},[76,510,511],{},"{'order': \u003COrder lines=2>, 'expected': 300, 'db_session': \u003CSession ...>}\n",[76,513,514],{"class":78,"line":91},[76,515,516],{},"(Pdb) !order.lines[1].qty = 2        # try the hypothesis directly\n",[76,518,519],{"class":78,"line":97},[76,520,521],{},"(Pdb) p order.total()\n",[76,523,524],{"class":78,"line":103},[76,525,526],{},"300\n",[10,528,529],{},"What the prompt cannot see is anything teardown would have produced — a rollback, a flushed log, a closed connection — because none of it has happened. That is usually an advantage, but it means an assertion about post-teardown state cannot be investigated this way.",[10,531,532,533,536,537,539],{},"It also cannot see other workers. Under ",[14,534,535],{},"pytest-xdist"," the worker process has no controlling terminal, so ",[14,538,60],{}," either hangs or errors. Re-run the single failing test serially, which is the standard move and also confirms whether the failure is parallel-specific.",[23,541,543],{"id":542},"making-the-session-part-of-the-workflow","Making the session part of the workflow",[10,545,546],{},"Three settings make the debugger cheap enough to reach for routinely.",[10,548,549,550,553,554,556],{},"Set ",[14,551,552],{},"PYTHONBREAKPOINT"," so a bare ",[14,555,297],{}," opens the debugger you actually want, without an import in the source:",[67,558,560],{"className":69,"code":559,"language":71,"meta":72,"style":72},"$ export PYTHONBREAKPOINT=ipdb.set_trace          # in your shell profile\n",[14,561,562],{"__ignoreMap":72},[76,563,564],{"class":78,"line":79},[76,565,559],{},[10,567,568,569,572,573,575,576,579],{},"Keep a project ",[14,570,571],{},".pdbrc"," with aliases for the objects you inspect constantly — a session, a request, a config — so the first three commands of every session are already typed. And add ",[14,574,60],{}," to a local-only pytest alias rather than to ",[14,577,578],{},"addopts",", because a debugger prompt in CI hangs the job until it times out.",[10,581,582,583,50],{},"For failures that only occur in CI or under parallel execution, the debugger is unavailable and the equivalent is a frame dump written to an artefact, which is covered in ",[46,584,586],{"href":585},"\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002Fpost-mortem-debugging-with-pdb-pm\u002F","post-mortem debugging with pdb.pm()",[23,588,590],{"id":589},"edge-cases-and-failure-modes","Edge cases and failure modes",[28,592,593,601,615,623,629,639],{},[31,594,595,600],{},[34,596,597,599],{},[14,598,60],{}," in CI hangs the job."," The prompt waits for input that never arrives; guard it behind a local-only alias or an environment check.",[31,602,603,606,607,610,611,614],{},[34,604,605],{},"Output capturing interferes with the prompt."," pytest disables capture while the debugger is open, but a test that already redirected ",[14,608,609],{},"sys.stdout"," itself may swallow the prompt; ",[14,612,613],{},"-s"," avoids it.",[31,616,617,622],{},[34,618,619,621],{},[14,620,181],{}," on a parametrized test opens once per case",", which with twenty cases is twenty prompts; select a single case by node id first.",[31,624,625,628],{},[34,626,627],{},"Continuing from the prompt still reports the failure."," The debugger does not change the outcome, so the test remains red — which is correct, and occasionally surprising.",[31,630,631,638],{},[34,632,633,634,637],{},"Fixtures with ",[14,635,636],{},"yield"," are mid-execution."," The teardown half has not run, so an object the fixture would have closed is still open and may behave differently from a fresh one.",[31,640,641,646],{},[34,642,643,645],{},[14,644,197],{}," needs the class importable at startup."," A typo produces a startup error rather than a fallback to plain pdb.",[23,648,650],{"id":649},"getting-to-the-failing-test-faster","Getting to the failing test faster",[10,652,653],{},"Opening a debugger is cheap; getting to the right failure is where the time goes. Four pytest flags shorten that loop, and they compose.",[10,655,656,659,660,663,664,666],{},[14,657,658],{},"--lf"," (last failed) reruns only the tests that failed in the previous run, and ",[14,661,662],{},"--ff"," (failed first) runs those first and then the rest. On a suite of five thousand tests with one failure, ",[14,665,658],{}," turns a four-minute loop into a two-second one:",[67,668,670],{"className":69,"code":669,"language":71,"meta":72,"style":72},"$ pytest -q                       # full run, three failures\n$ pytest --lf --pdb -x            # straight to the first failure, with a prompt\n",[14,671,672,677],{"__ignoreMap":72},[76,673,674],{"class":78,"line":79},[76,675,676],{},"$ pytest -q                       # full run, three failures\n",[76,678,679],{"class":78,"line":85},[76,680,681],{},"$ pytest --lf --pdb -x            # straight to the first failure, with a prompt\n",[10,683,684,687],{},[14,685,686],{},"--sw"," (stepwise) is the underrated one: it stops at the first failure and, on the next invocation, resumes from that test rather than starting over. Working through a list of failures after a refactor, it removes the re-running of everything that already passed:",[67,689,691],{"className":69,"code":690,"language":71,"meta":72,"style":72},"$ pytest --sw -q                  # stops at failure 1\n$ pytest --sw -q                  # fixes applied; resumes at failure 1, continues\n",[14,692,693,698],{"__ignoreMap":72},[76,694,695],{"class":78,"line":79},[76,696,697],{},"$ pytest --sw -q                  # stops at failure 1\n",[76,699,700],{"class":78,"line":85},[76,701,702],{},"$ pytest --sw -q                  # fixes applied; resumes at failure 1, continues\n",[10,704,705,707,708,710,711,714],{},[14,706,64],{}," stops at the first failure, which is what makes ",[14,709,60],{}," usable at all. And ",[14,712,713],{},"--co -q"," (collect only) is worth knowing for the case where the failure is in collection rather than in a test — a broken import in a conftest produces an error the debugger cannot reach, and the collection output names the file.",[10,716,717,718,721,722,724,725,727,728,731],{},"Combining them gives the tight loop: ",[14,719,720],{},"pytest --lf -x --pdb",". Run it, fix, run it again — each iteration goes directly to the current failure with a prompt open at the frame, and no time is spent on tests that already pass.\nA caution about ",[14,723,60],{}," and output capturing: pytest disables capture while the prompt is open, which means anything the test logged before the failure has already been swallowed unless ",[14,726,613],{}," was passed. When the logs matter as much as the state, run with ",[14,729,730],{},"-s --pdb -x"," so the output arrives in real time and the prompt opens with the log visible above it.",[23,733,735],{"id":734},"frequently-asked-questions","Frequently Asked Questions",[10,737,738,741,742,745],{},[34,739,740],{},"What does pytest --pdb actually give me?","\nA post-mortem prompt at the frame where the test failed, with every local still bound. It is the same state ",[14,743,744],{},"pdb.pm()"," would show, opened automatically and before fixture teardown runs.",[10,747,748,751,752,754],{},[34,749,750],{},"Why should --pdb be combined with -x?","\nBecause without it the debugger opens once per failing test, and a suite with fifty failures becomes fifty prompts. ",[14,753,64],{}," stops at the first failure, which is almost always the one you want.",[10,756,757,760,761,764,765,775,776,778,779,782,783,473,786,789],{},[34,758,759],{},"Can I use --pdb with pytest-xdist?","\nNo. Workers have no terminal attached, so the prompt cannot be shown. Re-run the failing test without ",[14,762,763],{},"-n",", or use the dump-frames approach for parallel-only failures.\n",[34,766,767,768,770,771,774],{},"Does ",[14,769,60],{}," work with ",[14,772,773],{},"pytest.raises"," blocks?","\nNot for the expected exception — a raise that ",[14,777,773],{}," catches is the test passing, so there is nothing to debug. It does open when the block fails for another reason: the wrong exception type, or no exception at all. To inspect the expected exception's state, capture it from the context manager (",[14,780,781],{},"with pytest.raises(X) as exc_info:",") and read ",[14,784,785],{},"exc_info.value",[14,787,788],{},"exc_info.traceback"," afterwards.",[209,791,793,862],{"className":792},[212],[214,794,222,799,222,802,222,805,222,808,222,810,222,813,222,817,222,821,222,826,222,829,222,831,222,835,222,838,222,842,222,844,222,847,222,850,222,853,222,855,222,859],{"viewBox":795,"role":217,"ariaLabelledBy":796,"xmlns":221},"0 0 760 320",[797,798],"pdbwhen-t","pdbwhen-d",[224,800,801],{"id":797},"When each debugging entry point is the right one",[228,803,804],{"id":798},"A stack of four situations and the matching pytest debugging entry point: an assertion failure, a wrong value with no exception, a failure in a fixture, and a failure that only happens in CI.",[250,806],{"x":252,"y":252,"width":253,"height":807,"rx":255,"fill":256},"320",[258,809,801],{"x":260,"y":261,"textAnchor":262,"fontSize":263,"fontWeight":264,"fill":265},[250,811],{"x":261,"y":812,"width":264,"height":386,"rx":389,"fill":273,"stroke":248,"strokeWidth":274},"56",[250,814],{"x":261,"y":812,"width":815,"height":386,"rx":816,"fill":248},"8","4",[258,818,820],{"x":386,"y":819,"fontSize":279,"fontWeight":264,"fill":265},"86","an assertion failed",[258,822,825],{"x":823,"y":819,"textAnchor":824,"fontSize":283,"fill":265},"714","end","--pdb: post-mortem at the failing frame",[250,827],{"x":261,"y":828,"width":264,"height":386,"rx":389,"fill":256,"stroke":390,"strokeWidth":274},"120",[250,830],{"x":261,"y":828,"width":815,"height":386,"rx":816,"fill":390},[258,832,834],{"x":386,"y":833,"fontSize":279,"fontWeight":264,"fill":265},"150","wrong value, no exception",[258,836,837],{"x":823,"y":833,"textAnchor":824,"fontSize":283,"fill":265},"--trace: step forward from the first line",[250,839],{"x":261,"y":840,"width":264,"height":386,"rx":389,"fill":273,"stroke":841,"strokeWidth":274},"184","#81b29a",[250,843],{"x":261,"y":840,"width":815,"height":386,"rx":816,"fill":841},[258,845,846],{"x":386,"y":293,"fontSize":279,"fontWeight":264,"fill":265},"a fixture raised",[258,848,849],{"x":823,"y":293,"textAnchor":824,"fontSize":283,"fill":265},"--pdb still works: the frame is in the fixture",[250,851],{"x":261,"y":852,"width":264,"height":386,"rx":389,"fill":256,"stroke":265,"strokeWidth":274},"248",[250,854],{"x":261,"y":852,"width":815,"height":386,"rx":816,"fill":265},[258,856,858],{"x":386,"y":857,"fontSize":279,"fontWeight":264,"fill":265},"278","only fails in CI",[258,860,861],{"x":823,"y":857,"textAnchor":824,"fontSize":283,"fill":265},"artefact dump, not a prompt",[335,863,864],{},"The last row is the boundary: a debugger needs a terminal, and CI does not have one.",[23,866,868],{"id":867},"related","Related",[28,870,871,877,883,890],{},[31,872,873,876],{},[46,874,875],{"href":48},"Interactive debugging with pdb and ipdb"," — the command vocabulary this prompt exposes.",[31,878,879,882],{},[46,880,881],{"href":585},"Post-mortem debugging with pdb.pm()"," — the same inspection without a terminal, for CI.",[31,884,885,889],{},[46,886,888],{"href":887},"\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002Fsetting-conditional-breakpoints-in-pdb\u002F","Setting conditional breakpoints in pdb"," — stopping on the iteration that matters.",[31,891,892,896],{},[46,893,895],{"href":894},"\u002Fadvanced-pytest-architecture-configuration\u002Foptimizing-test-discovery\u002Fpytest-xdist-vs-pytest-parallel-performance-comparison\u002F","pytest-xdist vs pytest-parallel performance comparison"," — why the debugger is unavailable under parallel runs.",[10,898,899,900],{},"← Back to ",[46,901,902],{"href":48},"Interactive Debugging with pdb and ipdb",[904,905,906],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":72,"searchDepth":85,"depth":85,"links":908},[909,910,911,912,913,914,915,916,917],{"id":25,"depth":85,"text":26},{"id":53,"depth":85,"text":54},{"id":340,"depth":85,"text":341},{"id":465,"depth":85,"text":466},{"id":542,"depth":85,"text":543},{"id":589,"depth":85,"text":590},{"id":649,"depth":85,"text":650},{"id":734,"depth":85,"text":735},{"id":867,"depth":85,"text":868},"Use pytest --pdb, --trace and --pdbcls to open a debugger at the failing frame: what state survives, fixture teardown interaction, and safe use in a parallel suite.","md",{"slug":921,"type":922,"breadcrumb":20,"datePublished":923,"dateModified":923,"faq":924,"howto":931},"dropping-into-pdb-on-test-failure","article","2026-08-01",[925,927,929],{"q":740,"a":926},"A post-mortem prompt at the frame where the test failed, with every local still bound. It is the same state pdb.pm() would show, opened automatically and before fixture teardown runs.",{"q":750,"a":928},"Because without it the debugger opens once per failing test, and a suite with fifty failures becomes fifty prompts. -x stops at the first failure, which is almost always the one you want.",{"q":759,"a":930},"No. Workers have no terminal attached, so the prompt cannot be shown. Re-run the failing test without -n, or use the dump-frames approach for parallel-only failures.",{"name":932,"description":933,"steps":934},"How to drop into pdb on a pytest failure","Open a debugger at the failing frame and inspect the state that produced it.",[935,938,941,944],{"name":936,"text":937},"Run with --pdb and -x","Stop at the first failure and open a post-mortem prompt there.",{"name":939,"text":940},"Inspect the frame and its locals","Use the standard pdb commands to read state and walk the stack.",{"name":942,"text":943},"Use --trace to break before the test runs","Open the prompt at the first line instead of at the failure.",{"name":945,"text":946},"Swap the prompt with --pdbcls","Point pytest at ipdb or another debugger class for a richer session.","\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002Fdropping-into-pdb-on-test-failure",{"title":5,"description":918},"systematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002Fdropping-into-pdb-on-test-failure\u002Findex","wGeNYop_73Rtt2kn7iAXpqpBt9pC1ggX_nVeU5ssHRI",1785613404395]