# 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 ``` Error: No document ID for SBOM found in pipeline logs ``` # Test source ```ts 423 | * @param cd - ArgoCD instance for managing deployments 424 | * @param environment - Target environment for deployment (e.g., 'dev', 'stage') 425 | * @param image - The container image tag/reference to use 426 | * @returns Promise that resolves when the image is built and deployed 427 | * @throws Error if the build or deployment process fails 428 | */ 429 | export async function fastMovingToBuildApplicationImage(git: Git, ci: CI): Promise { 430 | const gitType = git.getGitType(); 431 | try { 432 | logger.info(`Creating a direct commit on source repo on ${gitType} repository ...`); 433 | 434 | // Step 1: Create a direct commit to the main branch 435 | const commitSha = await git.createSampleCommitOnSourceRepo(); 436 | logger.info(`Created commit with SHA: ${commitSha}`); 437 | await sleep(10000); 438 | 439 | //TODO: Remove this once we are using Jenkins Plugins in the future 440 | if (ci.getCIType() === CIType.JENKINS) { 441 | // Step 2: Trigger the Jenkins job because we are not using Jenkins Plugins 442 | await (ci as JenkinsCI).triggerPipeline(git.getSourceRepoName()); 443 | } 444 | 445 | // Create a pull request object for pipeline reference only 446 | // Note: This is not an actual PR, just a reference object with the commit SHA 447 | const commitRef = new PullRequest(0, commitSha, git.getSourceRepoName()); 448 | 449 | // Step 2: Wait for pipeline to complete after commit 450 | await getPipelineAndWaitForCompletion( 451 | ci, 452 | commitRef, 453 | EventType.PUSH, 454 | `commit ${commitSha} on main branch in ${commitRef.repository}` 455 | ); 456 | } catch (error) { 457 | logger.error( 458 | `Error handling source repo code changes: ${error}` 459 | ); 460 | throw error; 461 | } 462 | } 463 | 464 | export async function buildApplicationImageWithPR(git: Git, ci: CI): Promise { 465 | const gitType = git.getGitType(); 466 | try { 467 | logger.info(`Creating a pull request on source repo on ${gitType} repository ...`); 468 | 469 | // Step 1: Create a PR which triggers a pipeline 470 | const pullRequest = await git.createSamplePullRequestOnSourceRepo(); 471 | logger.info(`Created PR ${pullRequest.url} with SHA: ${pullRequest.sha}`); 472 | 473 | // Step 2: Get the pipeline triggered by the PR and wait for complete 474 | await getPipelineAndWaitForCompletion( 475 | ci, 476 | pullRequest, 477 | EventType.PULL_REQUEST, 478 | `promotion PR #${pullRequest.pullNumber} in ${pullRequest.repository}` 479 | ); 480 | 481 | // TODO: Uncomment the following line when mergePullRequest is implemented 482 | //Step 3: If PR pipeline is successful, merge it and wait for the push pipeline 483 | const mergedPR = await git.mergePullRequest(pullRequest); 484 | 485 | //Step 4: Wait for Pipeline to complete after merged PR 486 | await getPipelineAndWaitForCompletion( 487 | ci, 488 | mergedPR, 489 | EventType.PUSH, 490 | `on-push pipeline after merging #${mergedPR.pullNumber} in ${mergedPR.repository}` 491 | ); 492 | } catch (error) { 493 | logger.error( 494 | `Error handling source repo code changes: ${error}` 495 | ); 496 | throw error; 497 | } 498 | } 499 | 500 | export async function handlePromotionToEnvironmentandGetPipeline( 501 | git: Git, 502 | ci: CI, 503 | cd: ArgoCD, 504 | environment: Environment, 505 | image: string 506 | ): Promise { 507 | // If CI is Jenkins, promote with directly creating commit 508 | // Else, promote with creating PR 509 | if (ci.getCIType() === CIType.JENKINS) { 510 | return await promoteWithoutPRAndGetPipeline(git, ci, cd, environment, image); 511 | } else { 512 | return await promoteWithPRAndGetPipeline(git, ci, cd, environment, image); 513 | } 514 | } 515 | 516 | export async function getSbomIDFromCIPipelineLogs(ci: CI, pipeline: Pipeline): Promise { 517 | try { 518 | logger.info(`Getting ${ci.getCIType()} Pipeline ${pipeline.id} logs to find SBOM document ID`); 519 | const pipelineLogs = await ci.getPipelineLogs(pipeline); 520 | 521 | const documentIdMatch = pipelineLogs.match(/"document_id"\s*:\s*"([^"]+)"/); 522 | if (!documentIdMatch) { > 523 | throw new Error('No document ID for SBOM found in pipeline logs'); | ^ Error: No document ID for SBOM found in pipeline logs 524 | } 525 | 526 | // Get the value "document_id" from match string 527 | const documentId = documentIdMatch[1]; 528 | logger.info(`SBOM Document ID ${documentId} found from Promotion Pipeline ${pipeline.id} logs`); 529 | return documentId; 530 | } catch (error) { 531 | logger.error(`Error getting pipeline Logs: ${error}`); 532 | throw error; 533 | } 534 | } 535 | 536 | /** 537 | * Searches for SBOM in TPA portal using document ID list 538 | * 539 | * @param tpa TPA instance for searching 540 | * @param sbomName The name of SBOM to search for 541 | * @param documentIdList Array of document IDs to search for 542 | * @returns Promise The first SBOM found or null if none found 543 | * @throws Error if TPA search fails or no valid document IDs provided 544 | */ 545 | export async function searchSBOMByNameAndDocIdList( 546 | tpa: TPA, 547 | sbomName: string, 548 | documentIdList: string[], 549 | ): Promise { 550 | // Validate input parameters 551 | if (!documentIdList || documentIdList.length === 0) { 552 | throw new Error('Document ID list cannot be empty'); 553 | } 554 | 555 | if (!tpa) { 556 | throw new Error('TPA instance is not initialised'); 557 | } 558 | 559 | const foundSbom: SBOMResult[] = []; 560 | const notFoundSbom: string[] = []; 561 | let sbom: SBOMResult | null = null; 562 | 563 | // Try to find SBOM using each document ID - search all for verification 564 | for (const documentId of documentIdList) { 565 | logger.info(`Attempting to search with document ID: ${documentId}`); 566 | 567 | try { 568 | sbom = await tpa.searchSBOMByNameAndDocID(sbomName, documentId); 569 | if (!sbom) { 570 | notFoundSbom.push(documentId); 571 | continue; 572 | } 573 | foundSbom.push(sbom); 574 | logger.info(`✅ SBOM found with document ID: ${documentId}`); 575 | logger.info(`SBOM details: Name: ${sbom.name}, Published: ${sbom.published}, SHA256: ${sbom.sha256}`); 576 | 577 | } catch (error) { 578 | logger.error(`❌ Error searching with document ID ${documentId}: ${error}`); 579 | throw error; 580 | } 581 | } 582 | if (notFoundSbom.length > 0) { 583 | logger.error(`⚠️ Failed to find SBOM Document ID ${notFoundSbom} in TPA`); 584 | return false; 585 | } 586 | logger.info(`✅ All SBOMS ${documentIdList} found in TPA!!!`); 587 | return true; 588 | } 589 | ```