# 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 >> Deployment Verification >> should promote and verify deployment to production environment - Location: tests/tssc/full_workflow.test.ts:121:5 # Error details ``` ArgoCDSyncError: Failed to sync ArgoCD application 'e2e-tests-java-springboot-sdiukvhc-prod': Failed to execute sync command: Command failed: argocd app sync 'e2e-tests-java-springboot-sdiukvhc-prod' --insecure --kube-context 'default/api-kx-68a17a8781-7z6t-p3-openshiftapps-com:443/cluster-admin' {"level":"fatal","msg":"rpc error: code = Unknown desc = POST https://tssc-gitops-server-tssc-gitops.apps.rosa.kx-68a17a8781.7z6t.p3.openshiftapps.com/application.ApplicationService/Sync failed with status code 504","time":"2026-05-13T17:41:48Z"} ``` # Test source ```ts 126 | const args = ['argocd', 'login', this.escapeShellArg(serverUrl)]; 127 | 128 | if (insecure) args.push('--insecure'); 129 | if (skipTestTls) args.push('--skip-test-tls'); 130 | if (grpcWeb) args.push('--grpc-web'); 131 | 132 | args.push('--username', this.escapeShellArg(username)); 133 | args.push('--password', this.escapeShellArg(password)); 134 | args.push('--kube-context', this.escapeShellArg(this.getKubeCurrentContext())); 135 | 136 | return args.join(' '); 137 | } 138 | 139 | private buildSyncCommand(applicationName: string, options: SyncOptions): string { 140 | // Build command with properly escaped arguments 141 | const args = ['argocd', 'app', 'sync', this.escapeShellArg(applicationName), '--insecure']; 142 | 143 | if (options.dryRun) args.push('--dry-run'); 144 | if (options.prune) args.push('--prune'); 145 | if (options.force) args.push('--force'); 146 | args.push('--kube-context', this.escapeShellArg(this.getKubeCurrentContext())); 147 | 148 | return args.join(' '); 149 | } 150 | 151 | private buildGetAppDetailsCommand(applicationName: string): string { 152 | // Build command with properly escaped arguments 153 | const args = ['argocd', 'app', 'get', this.escapeShellArg(applicationName), '--insecure']; 154 | args.push('--kube-context', this.escapeShellArg(this.getKubeCurrentContext())); 155 | 156 | return args.join(' '); 157 | } 158 | 159 | private async executeLogin(loginCmd: string, applicationName: string): Promise { 160 | const maxRetries = 5; 161 | 162 | await retry( 163 | async () => { 164 | try { 165 | const { stdout: _, stderr: loginErr } = await exec(loginCmd); 166 | if (loginErr && loginErr.trim()) { 167 | this.logger.warn(`ArgoCD login warnings: ${loginErr}`); 168 | } 169 | this.logger.info('Successfully logged into ArgoCD server'); 170 | } catch (loginError: any) { 171 | this.logger.error(`Error logging into ArgoCD: ${loginError.message}`); 172 | throw new ArgoCDConnectionError( 173 | `Failed to login to ArgoCD: ${loginError.message}`, 174 | loginError 175 | ); 176 | } 177 | }, 178 | { 179 | retries: maxRetries, 180 | minTimeout: 2000, 181 | factor: 2, 182 | onRetry: (error: Error, attempt: number) => { 183 | this.logger.warn(`[LOGIN-RETRY ${attempt}/${maxRetries}] Application: ${applicationName} | Status: Retrying login | Reason: ${error}`); 184 | }, 185 | } 186 | ); 187 | } 188 | 189 | private async executeSync(syncCmd: string, applicationName: string, namespace: string): Promise { 190 | const maxRetries = 5; 191 | 192 | await retry( 193 | async (bail) => { 194 | try { 195 | this.logger.info(`Executing sync command: ${syncCmd}`); 196 | const { stdout, stderr } = await exec(syncCmd); 197 | 198 | if (stderr && stderr.trim()) { 199 | this.logger.warn(`ArgoCD sync warnings: ${stderr}`); 200 | } 201 | if (stdout) { 202 | this.logger.info(`ArgoCD sync output:\n${stdout}`); 203 | } 204 | } catch (syncError: any) { 205 | this.logger.error(`Error executing sync command: ${syncError.message}`); 206 | 207 | // Get detailed application details for debugging 208 | try { 209 | await this.executeGetAppDetails(applicationName); 210 | const latestAppEvents = await this.applicationService.getApplicationEvents(applicationName, namespace); 211 | this.logger.error(`Getting latest application events:\n${latestAppEvents}`); 212 | } catch (statusError) { 213 | this.logger.error(`Unable to fetch application details for debug: ${statusError}`); 214 | } 215 | 216 | // Check if this is the "another operation is already in progress" error 217 | if (syncError.message && ( 218 | syncError.message.includes('another operation is already in progress') || 219 | syncError.message.includes('FailedPrecondition') 220 | )) { 221 | // This is a retryable error - throw to trigger retry 222 | throw new Error(`ArgoCD sync conflict: ${syncError.message}`); 223 | } 224 | 225 | // For other errors, use bail() to make them non-retryable > 226 | bail(new ArgoCDSyncError( | ^ ArgoCDSyncError: Failed to sync ArgoCD application 'e2e-tests-java-springboot-sdiukvhc-prod': Failed to execute sync command: Command failed: argocd app sync 'e2e-tests-java-springboot-sdiukvhc-prod' --insecure --kube-context 'default/api-kx-68a17a8781-7z6t-p3-openshiftapps-com:443/cluster-admin' 227 | applicationName, 228 | `Failed to execute sync command: ${syncError.message}`, 229 | syncError 230 | )); 231 | } 232 | }, 233 | { 234 | retries: maxRetries, 235 | minTimeout: 2000, 236 | factor: 2, 237 | maxTimeout: 30000, 238 | onRetry: (error: Error, attempt: number) => { 239 | this.logger.warn(`[SYNC-RETRY ${attempt}/${maxRetries}] Application: ${applicationName} | Status: Retrying sync | Reason: ${error}`); 240 | }, 241 | } 242 | ); 243 | } 244 | 245 | private async executeGetAppDetails(applicationName: string): Promise { 246 | const getAppDetailsCmd = this.buildGetAppDetailsCommand(applicationName); 247 | try { 248 | this.logger.info(`Executing command: ${getAppDetailsCmd}`); 249 | const { stdout, stderr } = await exec(getAppDetailsCmd); 250 | 251 | if (stderr && stderr.trim()) { 252 | this.logger.warn(`ArgoCD get app warnings:\n${stderr}`); 253 | } 254 | if (stdout) { 255 | this.logger.info(`ArgoCD get app:\n${stdout}`); 256 | } 257 | } catch (syncError: any) { 258 | this.logger.error(`Error executing app details command: ${syncError.message}`); 259 | throw new ArgoCDCliError( 260 | getAppDetailsCmd, 261 | syncError.code, 262 | syncError.message, 263 | syncError 264 | ); 265 | } 266 | } 267 | 268 | private async monitorSyncProcess( 269 | applicationName: string, 270 | namespace: string, 271 | timeoutMs: number, 272 | startTime: number 273 | ): Promise { 274 | const maxRetries = Math.floor(timeoutMs / 10000); 275 | 276 | const monitorSyncProcess = async (bail: (e: Error) => void): Promise => { 277 | // Check if we've exceeded the timeout 278 | if (Date.now() - startTime > timeoutMs) { 279 | const message = `Timeout reached after ${Math.round(timeoutMs / 1000 / 60)} minutes waiting for application ${applicationName} to sync`; 280 | bail(new ArgoCDTimeoutError('sync monitoring', timeoutMs)); 281 | return { 282 | success: false, 283 | message, 284 | health: 'Unknown', 285 | sync: 'Unknown', 286 | operationPhase: 'Unknown', 287 | }; 288 | } 289 | 290 | // Get current application status 291 | const healthStatus = await this.applicationService.getApplicationHealth(applicationName, namespace); 292 | const syncStatus = await this.applicationService.getApplicationSyncStatus(applicationName, namespace); 293 | const operationPhase = await this.applicationService.getApplicationOperationPhase(applicationName, namespace); 294 | 295 | // Check for success condition 296 | if (healthStatus === 'Healthy' && syncStatus === 'Synced') { 297 | this.logger.info(`Sync completed successfully for application ${applicationName} - Health: ${healthStatus}, Sync: ${syncStatus}`); 298 | return { 299 | success: true, 300 | message: `Sync completed successfully`, 301 | health: healthStatus, 302 | sync: syncStatus, 303 | operationPhase: operationPhase, 304 | }; 305 | } 306 | 307 | // Check for clear failure cases 308 | if ( 309 | healthStatus === 'Degraded' || 310 | syncStatus === 'SyncFailed' || 311 | operationPhase === 'Failed' || 312 | operationPhase === 'Error' 313 | ) { 314 | const errorMessage = `Sync failed for application ${applicationName} - Health: ${healthStatus}, Sync: ${syncStatus}, Operation: ${operationPhase}`; 315 | bail(new ArgoCDSyncError(applicationName, errorMessage)); 316 | return { 317 | success: false, 318 | message: errorMessage, 319 | health: healthStatus, 320 | sync: syncStatus, 321 | operationPhase: operationPhase, 322 | }; 323 | } 324 | 325 | // Still in progress 326 | const statusMsg = `Application ${applicationName} - Health: ${healthStatus}, Sync: ${syncStatus}, Operation: ${operationPhase}`; ```