# 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: Component creation failed after 3 attempts. Original name: 'e2e-tests-go-pbrfflwb'. Last error: Component creation failed: Failed to create Developer Hub component: AxiosError: Request failed with status code 404 ``` # Test source ```ts 29 | /invalid.*template/i, 30 | /permission denied/i, 31 | /environment variable .* is not defined or is empty/i, 32 | /failed to retrieve secret/i, 33 | ]; 34 | 35 | return nonRetryablePatterns.some((pattern) => pattern.test(errorMessage)); 36 | } 37 | 38 | /** 39 | * Creates a component and waits for completion with retry support. 40 | * 41 | * This function handles transient failures and name conflicts by: 42 | * 1. Creating the component with the current testItem name 43 | * 2. Waiting for component creation to complete (prints detailed task logs on failure) 44 | * 3. If either step fails, regenerating a new component name and retrying 45 | * 4. Continuing until success or max retries reached 46 | * 47 | * @param testItem The test item containing configuration details (name will be modified on retry) 48 | * @param options Retry configuration options 49 | * @returns Promise The successfully created and completed component 50 | * @throws Error when component creation fails after all retry attempts 51 | */ 52 | export async function createComponentAndWaitForCompletion( 53 | testItem: TestItem, 54 | options: { maxRetries?: number; retryDelayMs?: number; regenerateNameOnRetry?: boolean } = {} 55 | ): Promise { 56 | const DEFAULT_MAX_RETRIES = 3; 57 | const DEFAULT_RETRY_DELAY_MS = 10000; 58 | 59 | const { 60 | maxRetries = DEFAULT_MAX_RETRIES, 61 | retryDelayMs = DEFAULT_RETRY_DELAY_MS, 62 | regenerateNameOnRetry = true, 63 | } = options; 64 | 65 | let lastError: Error | null = null; 66 | let attemptNumber = 0; 67 | const originalName = testItem.getName(); 68 | const totalAttempts = maxRetries + 1; 69 | 70 | while (attemptNumber <= maxRetries) { 71 | attemptNumber++; 72 | const componentName = testItem.getName(); 73 | const imageName = componentName; 74 | 75 | logger.info(`[Attempt ${attemptNumber}/${totalAttempts}] Creating component '${componentName}'...`); 76 | 77 | try { 78 | // Step 1: Create the component 79 | const component = await Component.new(componentName, testItem, imageName, true); 80 | 81 | // Step 2: Wait for completion 82 | await component.waitUntilComponentIsCompleted(); 83 | 84 | // Success! 85 | logger.info(`✅ Component '${componentName}' created successfully on attempt ${attemptNumber}/${totalAttempts}`); 86 | 87 | return component; 88 | } catch (error) { 89 | lastError = error instanceof Error ? error : new Error(String(error)); 90 | const errorMessage = lastError.message; 91 | 92 | // Extract error summary (exclude task logs from summary display) 93 | const errorSummary = errorMessage.includes('---TASK_LOGS_START---') 94 | ? errorMessage.split('---TASK_LOGS_START---')[0].trim() 95 | : errorMessage; 96 | 97 | logger.error(`❌ COMPONENT CREATION FAILED - Attempt ${attemptNumber}/${totalAttempts} | Component: ${componentName} | Error: ${errorSummary}`); 98 | 99 | // Check if we've exhausted all retries 100 | if (attemptNumber > maxRetries) { 101 | logger.error(`❌ All ${totalAttempts} attempts exhausted. Original: '${originalName}', Last tried: '${componentName}'`); 102 | break; 103 | } 104 | 105 | // Check if this is a non-retryable error 106 | if (isNonRetryableError(errorMessage)) { 107 | logger.error(`Non-retryable error detected. Stopping retry attempts.`); 108 | break; 109 | } 110 | 111 | logger.info(`🔄 Will retry. Attempts remaining: ${totalAttempts - attemptNumber}`); 112 | 113 | // Regenerate name for next attempt if enabled 114 | if (regenerateNameOnRetry) { 115 | const newName = testItem.regenerateName(); 116 | logger.info(`New component name: '${newName}'`); 117 | 118 | // Save the new name immediately to project-configs.json 119 | await testItem.saveComponentName(); 120 | } 121 | 122 | // Wait before next retry 123 | logger.info(`Waiting ${retryDelayMs}ms before next attempt...`); 124 | await sleep(retryDelayMs); 125 | } 126 | } 127 | 128 | // If we reach here, all retries failed > 129 | throw new Error( | ^ Error: Component creation failed after 3 attempts. Original name: 'e2e-tests-go-pbrfflwb'. Last error: Component creation failed: Failed to create Developer Hub component: AxiosError: Request failed with status code 404 130 | `Component creation failed after ${attemptNumber} attempts. ` + 131 | `Original name: '${originalName}'. ` + 132 | `Last error: ${lastError?.message?.split('---TASK_LOGS_START---')[0].trim() || 'Unknown error'}` 133 | ); 134 | } 135 | 136 | /** 137 | * Promotes an application to a specific environment using a pull request workflow 138 | * 139 | * This function implements a GitOps promotion workflow by: 140 | * 1. Creating a promotion pull request in the GitOps repository for the target environment 141 | * 2. Waiting for any CI validation pipelines triggered by the PR to complete successfully 142 | * 3. Merging the PR once validations pass, which triggers deployment 143 | * 4. Syncing the ArgoCD application to deploy the changes to the target environment 144 | * 5. Waiting for the application to be fully synced with the new configuration 145 | * 146 | * The function handles error cases and provides appropriate error messages when 147 | * any step in the process fails. 148 | * 149 | * @param git - Git provider instance for interacting with repositories 150 | * @param ci - CI provider instance for monitoring pipeline execution 151 | * @param cd - ArgoCD instance for managing deployments 152 | * @param environment - Target environment to promote to (e.g. 'dev', 'stage', 'prod') 153 | * @param image - The container image URL to deploy to the target environment 154 | * @returns Promise that resolves when promotion is complete 155 | * @throws Error if any step in the promotion process fails 156 | */ 157 | export async function promoteWithPRAndGetPipeline( 158 | git: Git, 159 | ci: CI, 160 | cd: ArgoCD, 161 | environment: Environment, 162 | image: string 163 | ): Promise { 164 | logger.info(`Promoting application to ${environment} environment with pull request...`); 165 | try { 166 | // Step 1: Check if target environment's application exists 167 | const application = await cd.getApplication(environment); 168 | expect(application).not.toBeNull(); 169 | logger.info(`Application exists in ${environment} environment`); 170 | 171 | // Step 2: Create a promotion PR 172 | logger.info(`Creating promotion PR for ${environment} with image: ${image}`); 173 | const pr = await git.createPromotionPullRequestOnGitopsRepo(environment, image); 174 | logger.info(`Created promotion PR #${pr.pullNumber} in ${git.getGitOpsRepoName()} repository`); 175 | 176 | // Step 3: Wait for pipeline triggered by the promotion PR to complete 177 | const pipeline = await getPipelineAndWaitForCompletion( 178 | ci, 179 | pr, 180 | EventType.PULL_REQUEST, 181 | `promotion PR #${pr.pullNumber} in ${pr.repository}` 182 | ); 183 | 184 | // Step 4: Merge the PR when pipeline was successful 185 | const mergedPR = await git.mergePullRequest(pr); 186 | logger.info(`Merged promotion PR #${mergedPR.pullNumber} with SHA: ${mergedPR.sha}`); 187 | 188 | // Step 5: Sync and wait for the application to be ready 189 | const syncResult = await runAndWaitforAppSync(cd, environment, mergedPR.sha); 190 | expect(syncResult).toBe(true); 191 | 192 | logger.info(`Application successfully promoted to ${environment}`); 193 | 194 | return pipeline; 195 | } catch (error) { 196 | logger.error( 197 | `Error promoting application to ${environment}: ${error}` 198 | ); 199 | throw error; 200 | } 201 | } 202 | 203 | /** 204 | * Promotes an application to a specific environment by directly committing changes to the gitops repository 205 | * 206 | * This function: 207 | * 1. Directly modifies and commits changes to the gitops repository without creating a PR 208 | * 2. Syncs the ArgoCD application for the environment 209 | * 3. Waits for the application to be fully synced 210 | * 211 | * Note: This bypasses any CI checks and review processes that would normally be enforced by PRs 212 | * 213 | * @param git Git provider instance 214 | * @param ci CI provider instance (used for CI status observation only) 215 | * @param cd ArgoCD instance 216 | * @param environment Target environment to promote to 217 | * @param image The container image URL to deploy 218 | * @returns Promise that resolves when promotion is complete 219 | */ 220 | export async function promoteWithoutPRAndGetPipeline( 221 | git: Git, 222 | ci: CI, 223 | cd: ArgoCD, 224 | environment: Environment, 225 | image: string 226 | ): Promise { 227 | logger.info(`Promoting application to ${environment} environment with direct commit...`); 228 | 229 | try { ```