Skip to content

Commit 8ae28d4

Browse files
authored
Merge pull request #170 from woocommerce/24-05/send-custom-tests-results
Send custom tests results to Manager
2 parents 5ecba98 + 5e839d3 commit 8ae28d4

File tree

41 files changed

+1802
-106
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1802
-106
lines changed

_tests/custom_tests/tests/CompatibilityTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ public function test_multiple_tags_and_multiple_plugins_with_multiple_tags() {
103103

104104
$output = qit( [
105105
'run:e2e',
106-
'automatewoo:test:self-test-multiple-test-tags,self-test-multiple-test-tags-another',
106+
'automatewoo',
107+
'self-test-multiple-test-tags,self-test-multiple-test-tags-another',
107108
'--plugin',
108109
'woocommerce:test:self-test-multiple-test-tags,self-test-multiple-test-tags-another',
109110
] );

_tests/custom_tests/tests/EnvTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public function test_env_up_woocommerce_nightly_version() {
220220
'wp plugin get woocommerce',
221221
] );
222222

223-
$this->assertMatchesNormalizedSnapshot( $output );
223+
$this->assertStringContainsString( '-dev', $output );
224224
}
225225

226226
public function test_env_up_woocommerce_rc_version() {

_tests/custom_tests/tests/RunE2ETest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public function test_tag_and_run_test() {
4646

4747
$output = qit( [
4848
'run:e2e',
49-
'automatewoo:test:self-test-tag-and-run',
49+
'automatewoo',
50+
'self-test-tag-and-run',
5051
'--plugin',
5152
'woocommerce:activate',
5253
] );
@@ -73,7 +74,8 @@ public function test_multiple_tags_and_run_tests() {
7374

7475
$output = qit( [
7576
'run:e2e',
76-
'automatewoo:test:self-test-multiple-test-tags,self-test-multiple-test-tags-another',
77+
'automatewoo',
78+
'self-test-multiple-test-tags,self-test-multiple-test-tags-another',
7779
'--plugin',
7880
'woocommerce:activate',
7981
] );

_tests/custom_tests/tests/__snapshots__/EnvTest__test_env_up_woocommerce_nightly_version__1.txt

Lines changed: 0 additions & 8 deletions
This file was deleted.

qit

21.6 KB
Binary file not shown.

src/src/Commands/CustomTests/RunE2ECommand.php

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
use QIT_CLI\Environment\Environments\E2E\E2EEnvironment;
1717
use QIT_CLI\Environment\Environments\EnvInfo;
1818
use QIT_CLI\Environment\Environments\Environment;
19-
use QIT_CLI\Environment\Extension;
2019
use QIT_CLI\LocalTests\E2E\E2ETestManager;
20+
use QIT_CLI\LocalTests\LocalTestRunNotifier;
2121
use QIT_CLI\WooExtensionsList;
2222
use Symfony\Component\Console\Command\Command;
2323
use Symfony\Component\Console\Input\ArrayInput;
@@ -27,6 +27,7 @@
2727
use Symfony\Component\Console\Output\NullOutput;
2828
use Symfony\Component\Console\Output\OutputInterface;
2929
use Symfony\Component\Console\Output\StreamOutput;
30+
use Symfony\Component\Console\Style\SymfonyStyle;
3031
use function QIT_CLI\is_windows;
3132

3233
class RunE2ECommand extends DynamicCommand {
@@ -45,20 +46,25 @@ class RunE2ECommand extends DynamicCommand {
4546
/** @var WooExtensionsList */
4647
protected $woo_extensions_list;
4748

49+
/** @var LocalTestRunNotifier */
50+
protected $test_run_notifier;
51+
4852
protected static $defaultName = 'run:e2e'; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
4953

5054
public function __construct(
5155
E2EEnvironment $e2e_environment,
5256
Cache $cache,
5357
OutputInterface $output,
5458
E2ETestManager $e2e_test_manager,
55-
WooExtensionsList $woo_extensions_list
59+
WooExtensionsList $woo_extensions_list,
60+
LocalTestRunNotifier $test_run_notifier
5661
) {
5762
$this->e2e_environment = $e2e_environment;
5863
$this->cache = $cache;
5964
$this->output = $output;
6065
$this->e2e_test_manager = $e2e_test_manager;
6166
$this->woo_extensions_list = $woo_extensions_list;
67+
$this->test_run_notifier = $test_run_notifier;
6268
parent::__construct( static::$defaultName ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
6369
}
6470

@@ -74,8 +80,9 @@ protected function configure() {
7480
] );
7581

7682
$this
77-
->addArgument( 'woo_extension', InputArgument::OPTIONAL, 'A QIT plugin-syntax as defined in the documentation: source:action:test-tags:slug. Only "source" is required, and it can be a slug, a file, a URL. Action can be "activate", "bootstrap", and "test", and test-tags are a comme-separated list of tests. Slug is usually not required. Read the docs.' )
83+
->addArgument( 'woo_extension', InputArgument::OPTIONAL, 'The slug or WooCommerce ID of the main extension under test.' )
7884
->addArgument( 'test', InputArgument::OPTIONAL, '(Optional) The tests for the main extension under test. Accepts test tags, or a test directory. If not set, will use the "default" test tag of this extension.' )
85+
->addOption( 'source', null, InputOption::VALUE_OPTIONAL, 'The source of the main extension under test. Accepts a slug, a file, a URL. If not provided, the source will be the slug.' )
7986
->addOption( 'wp', null, InputOption::VALUE_OPTIONAL, 'The WordPress version. Accepts "stable", "nightly", or a version number.', 'stable' )
8087
->addOption( 'woo', null, InputOption::VALUE_OPTIONAL, 'The WooCommerce Version. Accepts "stable", "nightly", or a GitHub Tag (eg: 8.6.1).' )
8188
->addOption( 'plugin', 'p', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Plugin to activate in the environment. Accepts paths, Woo.com slugs/product IDs, WordPress.org slugs or GitHub URLs.', [] )
@@ -85,8 +92,10 @@ protected function configure() {
8592
->addOption( 'require', 'r', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Load PHP file before running the command (may be used more than once).' )
8693
->addOption( 'config', null, InputOption::VALUE_OPTIONAL, '(Optional) QIT config file to use.' )
8794
->addOption( 'object_cache', 'o', InputOption::VALUE_NONE, 'Whether to enable Object Cache (Redis) in the environment.' )
95+
->addOption( 'sut_action', null, InputOption::VALUE_OPTIONAL, 'What action to use for the main extension under test. Accepts "activate", "install" and "test". Default to "test"', 'test' )
8896
->addOption( 'no_activate', 's', InputOption::VALUE_NONE, 'Skip activating plugins in the environment.' )
8997
->addOption( 'shard', null, InputOption::VALUE_OPTIONAL, 'Playwright Sharding argument.' )
98+
->addOption( 'no_upload_report', null, InputOption::VALUE_NONE, 'Do not upload the report to QIT Manager.' )
9099
->addOption( 'update_snapshots', null, InputOption::VALUE_NONE, 'Update snapshots where applicable (eg: Playwright Snapshots).' )
91100
->addOption( 'pw_options', null, InputOption::VALUE_OPTIONAL, 'Additional options and parameters to pass to Playwright.' )
92101
->addOption( 'ui', null, InputOption::VALUE_NONE, 'Runs tests in UI mode. In this mode, you can start and view the tests running.' )
@@ -127,12 +136,15 @@ protected function execute( InputInterface $input, OutputInterface $output ): in
127136
$test_mode = E2ETestManager::$test_modes['headless'];
128137
}
129138

130-
$wait = $input->getOption( 'up_only' ) || $test_mode === E2ETestManager::$test_modes['codegen'];
131-
$woo_extension = $input->getArgument( 'woo_extension' );
132-
$test = $input->getArgument( 'test' );
133-
$shard = $input->getOption( 'shard' );
134-
$update_snapshots = $input->getOption( 'update_snapshots' );
135-
$pw_options = $input->getOption( 'pw_options' ) ?? '';
139+
$wait = $input->getOption( 'up_only' ) || $test_mode === E2ETestManager::$test_modes['codegen'];
140+
$woo_extension = $input->getArgument( 'woo_extension' );
141+
$test = $input->getArgument( 'test' );
142+
$woocommerce_version = $input->getOption( 'woo' );
143+
$shard = $input->getOption( 'shard' );
144+
$update_snapshots = $input->getOption( 'update_snapshots' );
145+
$pw_options = $input->getOption( 'pw_options' ) ?? '';
146+
$source = $input->getOption( 'source' ) ?? $woo_extension;
147+
$sut_action = $input->getOption( 'sut_action' );
136148

137149
if ( ! empty( $pw_options ) ) {
138150
// Remove wrapping double quotes if they exist.
@@ -148,39 +160,49 @@ protected function execute( InputInterface $input, OutputInterface $output ): in
148160
App::setVar( 'pw_options', $pw_options );
149161

150162
// Validate the extension is set if needed.
151-
if ( empty( $woo_extension ) && ! $wait ) {
152-
$output->writeln( '<error>The extension parameter is only optional in --up_only or --codegen modes.</error>' );
163+
if ( empty( $woo_extension ) ) {
164+
if ( ! empty( $source ) ) {
165+
$output->writeln( '<error>The extension parameter is required when the source parameter is set.</error>' );
153166

154-
return Command::INVALID;
167+
return Command::INVALID;
168+
}
169+
if ( ! empty( $sut_action ) ) {
170+
$output->writeln( '<error>The extension parameter is required when the sut_action parameter is set.</error>' );
171+
172+
return Command::INVALID;
173+
}
174+
if ( ! $wait ) {
175+
$output->writeln( '<error>The extension parameter is only optional in --up_only or --codegen modes.</error>' );
176+
177+
return Command::INVALID;
178+
}
155179
}
156180

157181
if ( ! empty( $woo_extension ) ) {
158-
if ( ! empty( $test ) ) {
159-
if ( ! file_exists( $test ) ) {
160-
$output->writeln( "<error>Test file '$test' does not exist.</error>" );
161-
162-
return Command::INVALID;
182+
// Validate WooExtension.
183+
try {
184+
if ( is_numeric( $woo_extension ) ) {
185+
$woo_extension = $this->woo_extensions_list->get_woo_extension_slug_by_id( $woo_extension );
186+
$woo_extension_id = $woo_extension;
187+
} else {
188+
$woo_extension_id = $this->woo_extensions_list->get_woo_extension_id_by_slug( $woo_extension );
163189
}
164-
$woo_extension = sprintf( '%s:test:%s', $woo_extension, realpath( $test ) );
165-
} else {
166-
$has_action = false;
190+
} catch ( \Exception $e ) {
191+
$output->writeln( sprintf( '<error>%s</error>', $e->getMessage() ) );
167192

168-
foreach ( Extension::ACTIONS as $action ) {
169-
if ( strpos( $woo_extension, ":$action" ) !== false ) {
170-
$has_action = true;
171-
break;
172-
}
173-
}
193+
return Command::INVALID;
194+
}
174195

175-
if ( ! $has_action ) {
176-
$woo_extension = "$woo_extension:test";
177-
}
196+
if ( ! empty( $test ) ) {
197+
$woo_extension_extension_syntax = sprintf( '%s:%s:base64%s', $woo_extension, $sut_action, base64_encode( $test ) );
198+
} else {
199+
$woo_extension_extension_syntax = sprintf( '%s:%s', $woo_extension, $sut_action );
178200
}
179201

180202
if ( $input->getOption( 'testing_theme' ) ) {
181-
$env_up_options['--theme'][] = $woo_extension;
203+
$env_up_options['--theme'][] = $woo_extension_extension_syntax;
182204
} else {
183-
$env_up_options['--plugin'][] = $woo_extension;
205+
$env_up_options['--plugin'][] = $woo_extension_extension_syntax;
184206
}
185207
}
186208

@@ -240,6 +262,11 @@ protected function execute( InputInterface $input, OutputInterface $output ): in
240262

241263
putenv( 'QIT_UP_AND_TEST=1' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv
242264
putenv( sprintf( 'QIT_SUT=%s', $woo_extension ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv
265+
if ( ! empty( $woo_extension_id ) ) {
266+
App::setVar( 'QIT_SUT', (int) $woo_extension_id );
267+
}
268+
269+
App::setVar( 'should_upload_report', ! $input->getOption( 'no_upload_report' ) );
243270

244271
$up_exit_status_code = $env_up_command->run(
245272
new ArrayInput( $env_up_options ),
@@ -259,6 +286,14 @@ protected function execute( InputInterface $input, OutputInterface $output ): in
259286
/** @var E2EEnvInfo $env_info */
260287
$env_info = E2EEnvInfo::from_array( $env_json );
261288

289+
App::singleton( E2EEnvInfo::class, $env_info );
290+
291+
if ( ! empty( $woo_extension_id ) ) {
292+
$env_info->sut_slug = $woo_extension;
293+
$env_info->sut_id = $woo_extension_id;
294+
$this->test_run_notifier->notify_test_started( $woo_extension_id, $woocommerce_version ?? 'none', $env_info );
295+
}
296+
262297
// Store in $GLOBALS so that's available in the shutdown function.
263298
$GLOBALS['env_to_shutdown'] = $env_info;
264299

@@ -271,11 +306,15 @@ protected function execute( InputInterface $input, OutputInterface $output ): in
271306

272307
$exit_status_code = $this->e2e_test_manager->run_tests( $env_info, $test_mode, $wait, $shard );
273308

309+
$io = new SymfonyStyle( $input, $output );
310+
274311
if ( $exit_status_code === Command::SUCCESS ) {
312+
$io->success( "Tests passed. Run 'qit e2e-report' to view the report." );
313+
275314
return Command::SUCCESS;
276315
} else {
277316
if ( $test_mode === E2ETestManager::$test_modes['headless'] ) {
278-
$this->output->writeln( sprintf( '<error>Tests failed. Exit status code: %s</error>', $exit_status_code ) );
317+
$io->error( "Tests failed. Run 'qit e2e-report' to view the report." );
279318
}
280319

281320
return Command::FAILURE;

src/src/Commands/CustomTests/ShowReportCommand.php

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,36 +28,53 @@ public function __construct( Cache $cache ) {
2828
protected function configure() {
2929
$this
3030
->addArgument( 'report_dir', InputArgument::OPTIONAL, '(Optional) The report directory. If not set, will show the last report.' )
31+
->addOption( 'local', null, null, 'Force showing the local report instead of the remote one.' )
3132
->addOption( 'dir_only', null, null, 'Only show the report directory.' )
3233
->setDescription( 'Shows a test report.' );
3334
}
3435

3536
protected function execute( InputInterface $input, OutputInterface $output ): int {
3637
if ( ! is_null( $input->getArgument( 'report_dir' ) ) ) {
37-
$report_dir = $input->getArgument( 'report_dir' );
38+
$local_report = $input->getArgument( 'report_dir' );
3839
} else {
39-
$report_dir = $this->cache->get( 'last_e2e_report' );
40+
$report_dir = json_decode( $this->cache->get( 'last_e2e_report' ) ?: '', true );
41+
42+
if ( empty( $report_dir ) ) {
43+
$output->writeln( 'No report found.' );
44+
45+
return Command::FAILURE;
46+
}
47+
48+
$local_report = $report_dir['local_playwright'];
49+
$remote_report = $report_dir['remote_qit'];
50+
}
51+
52+
// If it has both local and remote, show the remote, unless "force-local" is true.
53+
if ( ! $input->getOption( 'local' ) && ! empty( $remote_report ) ) {
54+
open_in_browser( $remote_report );
55+
56+
return Command::SUCCESS;
4057
}
4158

42-
if ( ! file_exists( $report_dir ) ) {
43-
throw new \RuntimeException( sprintf( 'Could not find the report directory: %s', $report_dir ) );
59+
if ( ! file_exists( $local_report ) ) {
60+
throw new \RuntimeException( sprintf( 'Could not find the report directory: %s', $local_report ) );
4461
}
4562

46-
if ( ! file_exists( $report_dir . '/index.html' ) ) {
47-
throw new \RuntimeException( sprintf( 'Could not find the report file: %s', $report_dir . '/index.html' ) );
63+
if ( ! file_exists( $local_report . '/index.html' ) ) {
64+
throw new \RuntimeException( sprintf( 'Could not find the report file: %s', $local_report . '/index.html' ) );
4865
}
4966

5067
if ( $input->getOption( 'dir_only' ) ) {
5168
// We usually want the "HTML" report, but here print the general result directory.
52-
$report_dir = dirname( $report_dir );
69+
$local_report = dirname( $local_report );
5370

54-
$output->writeln( $report_dir );
71+
$output->writeln( $local_report );
5572

5673
return Command::SUCCESS;
5774
}
5875

5976
try {
60-
$port = $this->start_server( $report_dir );
77+
$port = $this->start_server( $local_report );
6178
echo "Server started on port: $port\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
6279
} catch ( \RuntimeException $e ) {
6380
echo 'Error: ' . $e->getMessage() . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

src/src/Environment/Docker.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,74 @@ public function run_inside_docker( EnvInfo $env_info, array $command, array $env
198198
}
199199
}
200200

201+
/**
202+
* Copy a file from inside a Docker container to a local path in the filesystem.
203+
*
204+
* @param EnvInfo $env_info
205+
* @param string $container_path The file path inside the Docker container.
206+
* @param string $local_path The destination path on the local filesystem.
207+
* @param string $image The Docker image to use.
208+
*
209+
* @return void
210+
* @throws \RuntimeException If the file does not exist inside the Docker container.
211+
*/
212+
public function copy_from_docker( EnvInfo $env_info, string $container_path, string $local_path, string $image = 'php' ): void {
213+
$docker_container = $env_info->get_docker_container( $image );
214+
$docker_command = [ $this->find_docker(), 'exec', $docker_container, 'test', '-f', $container_path ];
215+
216+
$process = new Process( $docker_command );
217+
$process->setTimeout( 30 );
218+
$process->setIdleTimeout( 30 );
219+
220+
if ( $this->output->isVeryVerbose() ) {
221+
$this->output->writeln( $process->getCommandLine() );
222+
}
223+
224+
$process->run();
225+
226+
if ( ! $process->isSuccessful() ) {
227+
throw new \RuntimeException( "File $container_path does not exist in container $docker_container." );
228+
}
229+
230+
$docker_command = [ $this->find_docker(), 'cp', "$docker_container:$container_path", $local_path ];
231+
232+
$process = new Process( $docker_command );
233+
$process->setTimeout( 30 );
234+
$process->setIdleTimeout( 30 );
235+
236+
if ( $this->output->isVeryVerbose() ) {
237+
$this->output->writeln( $process->getCommandLine() );
238+
}
239+
240+
$process->run( function ( $type, $buffer ) {
241+
if ( $this->output->isVerbose() ) {
242+
$this->output->write( $buffer );
243+
}
244+
} );
245+
246+
if ( ! $process->isSuccessful() ) {
247+
$exit_code = $process->getExitCode();
248+
$output = $process->getOutput();
249+
$error_output = $process->getErrorOutput();
250+
251+
$message = "Failed to copy file from Docker container (Container $docker_container exited with $exit_code).";
252+
253+
if ( ! empty( $output ) ) {
254+
$message .= "\n" . $output;
255+
}
256+
257+
if ( ! empty( $error_output ) ) {
258+
$message .= "\n" . $error_output;
259+
}
260+
261+
if ( $this->output->isVerbose() ) {
262+
$message .= "\n" . 'Command that was executed: ' . $process->getCommandLine();
263+
}
264+
265+
throw new \RuntimeException( $message );
266+
}
267+
}
268+
201269
/**
202270
* @return bool Whether the user should be set in the docker command.
203271
*/

0 commit comments

Comments
 (0)