@@ -62,6 +62,9 @@ protected function tearDown(): void {
6262
6363 $ test_failed = $ this ->status () instanceof Failure || $ this ->status () instanceof Error;
6464
65+ // Collect SUT test artifacts before cleanup.
66+ $ this ->collectSutArtifacts ($ test_failed );
67+
6568 if ($ test_failed ) {
6669 $ this ->logNote ('Skipping cleanup as test has failed. ' );
6770 $ this ->log (static ::locationsInfo ());
@@ -79,6 +82,33 @@ protected function tearDown(): void {
7982 parent ::tearDown ();
8083 }
8184
85+ protected function collectSutArtifacts (bool $ test_failed ): void {
86+ // On failure, containers are still running but syncToHost() may not have
87+ // been called before the failure. Try to sync artifacts from the container.
88+ if ($ test_failed && !$ this ->volumesMounted ()) {
89+ try {
90+ $ this ->syncToHost ('.logs ' );
91+ }
92+ catch (\Throwable ) {
93+ // Ignore - container may not be running or .logs may not exist.
94+ }
95+ }
96+
97+ $ sut_logs = static ::$ sut . '/.logs ' ;
98+ if (!is_dir ($ sut_logs )) {
99+ return ;
100+ }
101+
102+ $ class_name = (new \ReflectionClass ($ this ))->getShortName ();
103+ $ test_id = $ class_name . '_ ' . $ this ->name () . '_ ' . uniqid ();
104+ $ test_id = (string ) preg_replace ('/[^a-zA-Z0-9_-]/ ' , '_ ' , $ test_id );
105+
106+ $ dest = dirname (__DIR__ , 2 ) . '/.logs/sut/ ' . $ test_id ;
107+ File::copy ($ sut_logs , $ dest );
108+
109+ $ this ->logNote ('SUT test artifacts copied to: ' . $ dest );
110+ }
111+
82112 /**
83113 * {@inheritdoc}
84114 */
0 commit comments