dag.test() runs every task of one DAG run in a single Python process, with no scheduler or executor, so a breakpoint in task code stops in your IDE (it replaced the DebugExecutor). It still records the run in the metadata database, so call it where Airflow 129 is configured, here inside a container. Options include logical_date, run_conf, conn_file_path and variable_file_path (secrets from local files) and mark_success_pattern, which marks matching tasks successful without running them:
dag = booknest_daily()
if __name__ == "__main__": # python booknest_daily.py: one local run
dag.test(logical_date=datetime(2026, 6, 30, 2, tzinfo=timezone.utc),
mark_success_pattern=r"model\.dbt_build") # fullmatch of task IDs: skip dbt$ docker exec l2-airflow-airflow-scheduler-1 python /opt/airflow/dags/booknest_daily.py extracted 195 orders for 2026-06-30 ... [DAG TEST] Marking success for <Task(BashOperator): model.dbt_build> on 2026-06-30 02:00:00+00: Done. Returned value was: 265 lines, gross 7176.40 Done. Returned value was: 6 DagRun Finished: dag_id=booknest_daily, logical_date=2026-06-30 02:00:00+00:00, run_id=manual__ took 18 s
The run took 18 seconds against 74 with dbt 37,942 . The first attempt passed "dbt_build", which matched nothing, because the pattern must match the whole task ID, group prefix included. airflow dags test <dag_id> <date> (Operators vs TaskFlow) does the same from the CLI. Skipping a step is safe here only because check verifies the fact table that an earlier run built; in CI, run the full DAG against a disposable database.