# Instructions - Following Playwright test failed. - Explain why, be concise, respect Playwright best practices. - Provide a snippet of code with the fix, if possible. # Test info - Name: tssc/full_workflow.test.ts >> TSSC Complete Workflow >> Component Creation >> should create a component successfully - Location: tests/tssc/full_workflow.test.ts:45:5 # Error details ``` Error: Failed to retrieve Developer Hub task status: Error: socket hang up ``` # Test source ```ts 1 | import { ScaffolderScaffoldOptions, ScaffolderTask } from '@backstage/plugin-scaffolder-react'; 2 | import retry from 'async-retry'; 3 | import axios, { Axios, AxiosResponse } from 'axios'; 4 | import * as https from 'https'; 5 | import { LoggerFactory, Logger } from '../../logger/logger'; 6 | 7 | // Define the expected response type from the Developer Hub API 8 | interface ComponentIdResponse { 9 | id: string; 10 | } 11 | 12 | export class DeveloperHub { 13 | private readonly url: string; 14 | private readonly axios: Axios; 15 | private readonly logger: Logger; 16 | 17 | public constructor(url: string) { 18 | if (!url) { 19 | throw new Error('Cannot initialize DeveloperHubClient without a URL'); 20 | } 21 | this.url = url; 22 | this.axios = axios.create({ 23 | httpAgent: new https.Agent({ 24 | rejectUnauthorized: false, 25 | }), 26 | timeout: 30000, // Global timeout for all requests (30 seconds) 27 | }); 28 | this.logger = LoggerFactory.getLogger('rhdh.developer-hub'); 29 | this.logger.info(`Initialized Developer Hub client`); 30 | } 31 | 32 | public getUrl(): string { 33 | return this.url; 34 | } 35 | 36 | /** 37 | * Creates a component in Developer Hub 38 | * @param componentScaffoldOptions Options for scaffolding the component 39 | * @returns Promise resolving to the task ID and status 40 | * @throws Error if the component creation fails 41 | */ 42 | public async createComponent( 43 | componentScaffoldOptions: ScaffolderScaffoldOptions 44 | ): Promise { 45 | try { 46 | this.logger.info(`Creating component with options: ${componentScaffoldOptions}`); 47 | const response: AxiosResponse = await this.axios.post( 48 | `${this.url}/api/scaffolder/v2/tasks`, 49 | componentScaffoldOptions 50 | ); 51 | 52 | return response.data; 53 | } catch (error) { 54 | throw new Error(`Failed to create Developer Hub component: ${error}`); 55 | } 56 | } 57 | 58 | /** 59 | * Retrieves the status of a task from Developer Hub 60 | * @param taskId ID of the task to retrieve 61 | * @returns Promise resolving to the task status 62 | * @throws Error if the task retrieval fails 63 | */ 64 | public async getComponent(componentId: string): Promise { 65 | try { 66 | const response: AxiosResponse = await this.axios.get( 67 | `${this.url}/api/scaffolder/v2/tasks/${componentId}` 68 | ); 69 | 70 | return response.data; 71 | } catch (error) { > 72 | throw new Error(`Failed to retrieve Developer Hub task status: ${error}`); | ^ Error: Failed to retrieve Developer Hub task status: Error: socket hang up 73 | } 74 | } 75 | 76 | /** 77 | * Retrieves the status of a component creation task from Developer Hub 78 | * @param componentId ID of the component task to retrieve status for 79 | * @returns Promise resolving to the task status, status: 'failed' | 'completed' | 'processing' | 'open' | 'cancelled' 80 | * @throws Error if the task status retrieval fails 81 | */ 82 | public async getComponentStatus(componentId: string): Promise { 83 | const component = await this.getComponent(componentId); 84 | return component.status; 85 | } 86 | 87 | /** 88 | * Retrieves the logs of a component creation task from Developer Hub 89 | * @param componentId ID of the component task to retrieve logs for 90 | * @returns Promise resolving to the task logs 91 | * @throws Error if the task log retrieval fails 92 | */ 93 | public async getComponentLogs(componentId: string): Promise { 94 | try { 95 | const response: AxiosResponse = await this.axios.get( 96 | `${this.url}/api/scaffolder/v2/tasks/${componentId}/eventstream` 97 | ); 98 | 99 | return response.data; 100 | } catch (error) { 101 | throw new Error(`Failed to retrieve Developer Hub task logs: ${error}`); 102 | } 103 | } 104 | 105 | /** 106 | * Waits until a component creation task is completed, failed, or cancelled 107 | * 108 | * This method polls the component status until it reaches a terminal state 109 | * (completed, failed, or cancelled). It uses exponential backoff for retries 110 | * and provides detailed logging throughout the process. 111 | * 112 | * @param taskId ID of the component task to wait for 113 | * @returns Promise resolving when the task has reached a terminal state 114 | * @throws Error if the task fails or is cancelled, or if max retries are exceeded 115 | */ 116 | public async waitUntilComponentIsCompleted(taskId: string): Promise { 117 | this.logger.info(`Waiting for component creation task ${taskId} to complete...`); 118 | 119 | // Define the operation that will be retried 120 | const checkComponentStatus = async (bail: (e: Error) => void): Promise => { 121 | try { 122 | // Get the latest task status 123 | const taskStatus = await this.getComponent(taskId); 124 | const status = taskStatus.status; 125 | 126 | this.logger.info(`Component creation status: ${status}`); 127 | 128 | // Check if the task has reached a terminal state 129 | if (status === 'completed') { 130 | this.logger.info('Component was created successfully!'); 131 | return; 132 | } else if (status === 'failed' || status === 'cancelled') { 133 | this.logger.error(`Component creation ${status}.`, { taskId }); 134 | 135 | // Get logs to understand what went wrong and include in error 136 | let taskLogs = ''; 137 | try { 138 | taskLogs = await this.getComponentLogs(taskId); 139 | this.logger.error(`Task logs:\n${taskLogs}`); 140 | } catch (logError) { 141 | this.logger.error(`Failed to retrieve task logs: ${logError instanceof Error ? logError.message : String(logError)}`); 142 | } 143 | 144 | const errorMessage = taskLogs 145 | ? `Component creation ${status} for task ${taskId}\n---TASK_LOGS_START---\n${taskLogs}\n---TASK_LOGS_END---` 146 | : `Component creation ${status} for task ${taskId}`; 147 | 148 | // Use bail to immediately exit the retry loop for terminal failure states 149 | bail(new Error(errorMessage)); 150 | return; // This line won't be reached after bail, but added for clarity 151 | } 152 | 153 | // If still processing or open, throw error to trigger retry 154 | throw new Error(`Component creation still in progress (status: ${status})`); 155 | } catch (error) { 156 | // For errors that indicate a problem with the API call itself, 157 | // we might want to handle them differently (e.g., network errors) 158 | if (error instanceof Error && error.message.includes('Failed to retrieve')) { 159 | this.logger.warn(`API error while checking component status: ${error}`); 160 | } 161 | 162 | // Re-throw the error to trigger retry 163 | throw error; 164 | } 165 | }; 166 | 167 | const maxRetries = 24; // Maximum number of retries 168 | const timeout = 5000; 169 | 170 | try { 171 | await retry(checkComponentStatus, { 172 | retries: maxRetries, ```