From 51c54ab2ef2b64eef52bc63f12db49d227bd9808 Mon Sep 17 00:00:00 2001 From: Tomas Kral Date: Thu, 5 Feb 2026 13:59:24 +0100 Subject: [PATCH 1/2] fix: resolve e2e test failures for community plugin build The e2e tests were failing due to three issues: - isolated-vm native module build failures during yarn install - Running yarn install from plugin directory instead of workspace root - Missing TypeScript declaration files required for plugin builds This fix: - Uses YARN_ENABLE_SCRIPTS=false to skip native module builds - Installs dependencies from workspace root (workspaces/tech-radar) - Runs yarn tsc at workspace level before building individual plugins - Improves error logging to show detailed failure information Assisted-by: Claude Code Signed-off-by: Tomas Kral --- .../community-plugin-build-package.test.ts | 62 ++++++++++++------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/e2e-tests/community-plugin-build-package.test.ts b/e2e-tests/community-plugin-build-package.test.ts index c5490d6..c55d7e1 100644 --- a/e2e-tests/community-plugin-build-package.test.ts +++ b/e2e-tests/community-plugin-build-package.test.ts @@ -35,19 +35,29 @@ async function runCommand( `Executing command: ${command}, in directory: ${options.cwd || process.cwd()}`, ); - const { err, stdout, stderr } = await exec(command, { - shell: true, - ...options, - }); - console.log(`Command output: ${stdout}`); - console.log(`Command error output: ${stderr}`); - if (err) { - console.error(`Error executing command: ${command}`); - console.error(stderr); - console.error(stdout); - throw err; + try { + const { stdout, stderr } = await exec(command, { + shell: true, + maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs + ...options, + }); + console.log(`Command output: ${stdout}`); + if (stderr) { + console.log(`Command stderr: ${stderr}`); + } + return { stdout, stderr }; + } catch (error: any) { + console.error(`\n========== COMMAND FAILED ==========`); + console.error(`Command: ${command}`); + console.error(`Working directory: ${options.cwd || process.cwd()}`); + console.error(`Exit code: ${error.code}`); + console.error(`Signal: ${error.signal}`); + console.error(`\n--- STDOUT ---\n${error.stdout || '(empty)'}`); + console.error(`\n--- STDERR ---\n${error.stderr || '(empty)'}`); + console.error(`\n--- ERROR MESSAGE ---\n${error.message}`); + console.error(`====================================\n`); + throw error; } - return { stdout, stderr }; } async function parseDynamicPluginAnnotation( @@ -136,23 +146,33 @@ describe('export and package backstage-community plugin', () => { describe.each([ [ - 'workspaces/tech-radar/plugins/tech-radar', + 'workspaces/tech-radar', + 'plugins/tech-radar', `rhdh-test-tech-radar-frontend:${Date.now()}`, ], [ - 'workspaces/tech-radar/plugins/tech-radar-backend', + 'workspaces/tech-radar', + 'plugins/tech-radar-backend', `rhdh-test-tech-radar-backend:${Date.now()}`, ], - ])('plugin in %s directory', (pluginPath, imageTag) => { - const getFullPluginPath = () => path.join(getClonedRepoPath(), pluginPath); + ])('plugin in %s/%s directory', (workspacePath, pluginRelPath, imageTag) => { + const getWorkspacePath = () => path.join(getClonedRepoPath(), workspacePath); + const getFullPluginPath = () => + path.join(getClonedRepoPath(), workspacePath, pluginRelPath); beforeAll(async () => { - console.log(`Installing dependencies in ${getFullPluginPath()}`); - await runCommand(`yarn install`, { - cwd: getFullPluginPath(), + console.log(`Installing dependencies in workspace ${getWorkspacePath()}`); + // Use YARN_ENABLE_SCRIPTS=false to skip native module builds that may fail + // Then run tsc and build separately + await runCommand(`YARN_ENABLE_SCRIPTS=false yarn install`, { + cwd: getWorkspacePath(), + }); + console.log(`Generating TypeScript declarations in ${getWorkspacePath()}`); + await runCommand(`yarn tsc`, { + cwd: getWorkspacePath(), }); - console.log(`Compiling TypeScript in ${getFullPluginPath()}`); - await runCommand(`npx tsc`, { + console.log(`Building plugin in ${getFullPluginPath()}`); + await runCommand(`yarn build`, { cwd: getFullPluginPath(), }); }); From 94cc731b92d28de986e591b9ec14dd42c6b5c53a Mon Sep 17 00:00:00 2001 From: Stan Lewis Date: Tue, 17 Mar 2026 15:25:10 -0400 Subject: [PATCH 2/2] fix(e2e): show command output on error (#84) This change updates the CLI e2e test spec so that it prints the full output of whatever command fails during execution. This change also includes some tidying of the e2e test output in general so it's hopefully easier to follow, for example avoiding jest's console.log wrapper. Assisted-by: Cursor Desktop rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --- .../community-plugin-build-package.test.ts | 104 ++++++++++-------- e2e-tests/jest.config.js | 1 + e2e-tests/setup.js | 7 ++ 3 files changed, 66 insertions(+), 46 deletions(-) create mode 100644 e2e-tests/setup.js diff --git a/e2e-tests/community-plugin-build-package.test.ts b/e2e-tests/community-plugin-build-package.test.ts index c55d7e1..f494160 100644 --- a/e2e-tests/community-plugin-build-package.test.ts +++ b/e2e-tests/community-plugin-build-package.test.ts @@ -9,8 +9,18 @@ const exec = promisify(require('child_process').exec); const CONTAINER_TOOL = process.env.CONTAINER_TOOL || 'podman'; +const LOG_PREFIX = '[e2e]'; + +function log(msg: string): void { + console.log(`${LOG_PREFIX} ${msg}`); +} + +function logSection(title: string): void { + console.log(`${LOG_PREFIX} --- ${title} ---`); +} + async function downloadFile(url: string, file: string): Promise { - console.log(`Downloading file from ${url} to ${file}`); + log(`Downloading ${url} -> ${file}`); const response = await axios({ method: 'GET', url: url, @@ -31,9 +41,7 @@ async function runCommand( command: string, options: { cwd?: string } = {}, ): Promise<{ stdout: string; stderr: string }> { - console.log( - `Executing command: ${command}, in directory: ${options.cwd || process.cwd()}`, - ); + const cwd = options.cwd || process.cwd(); try { const { stdout, stderr } = await exec(command, { @@ -41,22 +49,32 @@ async function runCommand( maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs ...options, }); - console.log(`Command output: ${stdout}`); - if (stderr) { - console.log(`Command stderr: ${stderr}`); - } return { stdout, stderr }; - } catch (error: any) { - console.error(`\n========== COMMAND FAILED ==========`); - console.error(`Command: ${command}`); - console.error(`Working directory: ${options.cwd || process.cwd()}`); - console.error(`Exit code: ${error.code}`); - console.error(`Signal: ${error.signal}`); - console.error(`\n--- STDOUT ---\n${error.stdout || '(empty)'}`); - console.error(`\n--- STDERR ---\n${error.stderr || '(empty)'}`); - console.error(`\n--- ERROR MESSAGE ---\n${error.message}`); - console.error(`====================================\n`); - throw error; + } catch (err: unknown) { + const e = err as { + code?: string | number; + signal?: string; + stdout?: string; + stderr?: string; + }; + const out = (e.stdout ?? '').trim() || '(empty)'; + const errOut = (e.stderr ?? '').trim() || '(empty)'; + const enrichedMessage = [ + `Command failed: ${command}`, + `Cwd: ${cwd}`, + `Exit code: ${e.code ?? 'N/A'} | Signal: ${e.signal ?? 'N/A'}`, + '--- stdout ---', + out, + '--- stderr ---', + errOut, + ].join('\n'); + + console.error(`${LOG_PREFIX} COMMAND FAILED: ${command}`); + console.error(`${LOG_PREFIX} cwd: ${cwd}`); + console.error(`${LOG_PREFIX} --- stdout ---\n${out}`); + console.error(`${LOG_PREFIX} --- stderr ---\n${errOut}`); + + throw new Error(enrichedMessage); } } @@ -98,9 +116,10 @@ describe('export and package backstage-community plugin', () => { jest.setTimeout(TEST_TIMEOUT); beforeAll(async () => { - console.log(`Using rhdh-cli at: ${RHDH_CLI}`); - console.log(`Test workspace: ${tmpDir}`); - console.log(`Container tool: ${CONTAINER_TOOL}`); + logSection('Setup'); + log(`rhdh-cli: ${RHDH_CLI}`); + log(`workspace: ${tmpDir}`); + log(`container tool: ${CONTAINER_TOOL}`); let communityPluginsArchivePath = path.join( tmpDir, @@ -109,26 +128,20 @@ describe('export and package backstage-community plugin', () => { if (process.env.COMMUNITY_PLUGINS_REPO_ARCHIVE) { communityPluginsArchivePath = process.env.COMMUNITY_PLUGINS_REPO_ARCHIVE; - console.log( - `Using community plugins repo archive: ${communityPluginsArchivePath}`, - ); + log(`Community plugins: path from env: ${communityPluginsArchivePath}`); } - if (!fs.existsSync(communityPluginsArchivePath)) { - console.log(`Downloading community plugins archive from: ${REPO_URL}`); - await downloadFile(REPO_URL, communityPluginsArchivePath); - console.log( - `Downloaded community plugins archive to: ${communityPluginsArchivePath}`, + if (fs.existsSync(communityPluginsArchivePath)) { + log( + `Community plugins: using existing archive (skipping download): ${communityPluginsArchivePath}`, ); } else { - console.log( - `Using existing community plugins archive: ${communityPluginsArchivePath}`, - ); + log(`Community plugins: archive not found, downloading from ${REPO_URL}`); + await downloadFile(REPO_URL, communityPluginsArchivePath); + log(`Community plugins: downloaded to ${communityPluginsArchivePath}`); } - console.log( - `Extracting community plugins archive to: ${getClonedRepoPath()}`, - ); + log(`Community plugins: extracting to ${getClonedRepoPath()}`); fs.mkdirSync(getClonedRepoPath(), { recursive: true }); await tar.x({ file: communityPluginsArchivePath, @@ -156,29 +169,30 @@ describe('export and package backstage-community plugin', () => { `rhdh-test-tech-radar-backend:${Date.now()}`, ], ])('plugin in %s/%s directory', (workspacePath, pluginRelPath, imageTag) => { - const getWorkspacePath = () => path.join(getClonedRepoPath(), workspacePath); + const getWorkspacePath = () => + path.join(getClonedRepoPath(), workspacePath); const getFullPluginPath = () => path.join(getClonedRepoPath(), workspacePath, pluginRelPath); beforeAll(async () => { - console.log(`Installing dependencies in workspace ${getWorkspacePath()}`); + logSection(`Plugin: ${workspacePath}/${pluginRelPath}`); + log(`Installing dependencies in workspace ${getWorkspacePath()}`); // Use YARN_ENABLE_SCRIPTS=false to skip native module builds that may fail - // Then run tsc and build separately await runCommand(`YARN_ENABLE_SCRIPTS=false yarn install`, { cwd: getWorkspacePath(), }); - console.log(`Generating TypeScript declarations in ${getWorkspacePath()}`); + log(`Generating TypeScript declarations in ${getWorkspacePath()}`); await runCommand(`yarn tsc`, { cwd: getWorkspacePath(), }); - console.log(`Building plugin in ${getFullPluginPath()}`); + log(`Building plugin in ${getFullPluginPath()}`); await runCommand(`yarn build`, { cwd: getFullPluginPath(), }); }); afterAll(async () => { - console.log(`Cleaning up image: ${imageTag}`); + log(`Cleaning up image: ${imageTag}`); await runCommand(`${CONTAINER_TOOL} rmi -f ${imageTag}`); }); @@ -218,10 +232,8 @@ describe('export and package backstage-community plugin', () => { ); const imageMetadata = await getImageMetadata(imageTag); - console.log( - `Image annotations: ${JSON.stringify(imageMetadata.annotations)}`, - ); - console.log(`Image labels: ${JSON.stringify(imageMetadata.labels)}`); + log(`Image annotations: ${JSON.stringify(imageMetadata.annotations)}`); + log(`Image labels: ${JSON.stringify(imageMetadata.labels)}`); // There needs to be at least one annotation (the default dynamic plugin annotation) expect(imageMetadata.annotations).not.toBeNull(); diff --git a/e2e-tests/jest.config.js b/e2e-tests/jest.config.js index 7ea56d0..c4d1dd7 100644 --- a/e2e-tests/jest.config.js +++ b/e2e-tests/jest.config.js @@ -5,6 +5,7 @@ const tsJestTransformCfg = createDefaultPreset().transform; /** @type {import("jest").Config} **/ module.exports = { testEnvironment: 'node', + setupFilesAfterEnv: ['/setup.js'], transform: { ...tsJestTransformCfg, }, diff --git a/e2e-tests/setup.js b/e2e-tests/setup.js new file mode 100644 index 0000000..e8fc715 --- /dev/null +++ b/e2e-tests/setup.js @@ -0,0 +1,7 @@ +// Plain console output for e2e tests (no Jest "console.log" label or stack trace) +console.log = (...args) => { + process.stdout.write(args.map(String).join(' ') + '\n'); +}; +console.error = (...args) => { + process.stderr.write(args.map(String).join(' ') + '\n'); +};