# 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: ui/component.test.ts >> Component UI Test Suite >> Verify CI >> verify CI provider on CI tab - Location: tests/ui/component.test.ts:83:5 # Error details ``` TimeoutError: Step timeout of 40000ms exceeded. ``` ``` Error: locator.scrollIntoViewIfNeeded: Test ended. Call log: - waiting for locator('table').filter({ has: getByRole('columnheader', { name: 'NAME' }) }).getByRole('columnheader', { name: 'ACTIONS', exact: true }) ``` # Page snapshot ```yaml - generic [ref=e3]: - navigation [ref=e5]: - generic [ref=e6]: - link "Home" [ref=e8] [cursor=pointer]: - /url: / - img [ref=e9] - generic [ref=e18]: - img [ref=e20] - combobox "Search..." [ref=e22] - generic "Self-service" [ref=e25]: - link "Self-service" [ref=e26] [cursor=pointer]: - /url: /create - img [ref=e28] - button "Your starred items" [ref=e31] [cursor=pointer]: - img [ref=e32] - button "Application launcher" [ref=e35] [cursor=pointer]: - img [ref=e36] - button "Help" [ref=e39] [cursor=pointer]: - img [ref=e40] - separator [ref=e42] - button "Admin" [ref=e44] [cursor=pointer]: - generic [ref=e45]: - img [ref=e46] - paragraph [ref=e49]: Admin - img [ref=e50] - generic [ref=e53]: - navigation "sidebar nav": - generic [ref=e55]: - generic [ref=e58]: - link "Home" [ref=e60] [cursor=pointer]: - /url: / - img [ref=e64] - generic [ref=e66]: Home - link "Catalog" [ref=e68] [cursor=pointer]: - /url: /catalog - img [ref=e72] - generic [ref=e74]: Catalog - link "APIs" [ref=e76] [cursor=pointer]: - /url: /api-docs - img [ref=e80] - generic [ref=e82]: APIs - link "Learning Paths" [ref=e84] [cursor=pointer]: - /url: /learning-paths - img [ref=e88] - generic [ref=e90]: Learning Paths - separator [ref=e91] - link "Docs" [ref=e94] [cursor=pointer]: - /url: /docs - img [ref=e98] - generic [ref=e100]: Docs - generic [ref=e101]: - separator [ref=e102] - button "Administration" [ref=e103] [cursor=pointer]: - generic [ref=e104]: - img [ref=e108] - generic [ref=e111]: Administration - img [ref=e113] - main [ref=e115]: - generic [ref=e117]: - paragraph [ref=e118]: component - heading "backend-tests-go-wijgfgsg" [level=1] [ref=e119]: - generic [ref=e121]: backend-tests-go-wijgfgsg - article [ref=e122]: - alert [ref=e123]: - 'button "Warning: Entity not found" [ref=e124] [cursor=pointer]': - generic [ref=e125]: - img [ref=e126] - 'heading "Warning: Entity not found" [level=6] [ref=e128]' - img [ref=e131] ``` # Test source ```ts 25 | 26 | for (const task of TektonPO.sourceTasks) { 27 | const button = page.getByRole('heading', { name: task, exact: true }); 28 | await expect(button).toBeVisible(); 29 | } 30 | 31 | const button = page.getByRole('heading', { name: TektonPO.sourceTasks[0] }); 32 | await button.click(); 33 | 34 | // Check the log is visible by looking for the word 'STEP' 35 | const span = page.getByText(TektonPO.logStepRegex).first(); 36 | await expect(span).toBeVisible(); 37 | 38 | // Close popup 39 | const closeButton = page.getByRole('dialog').getByTestId(CommonPO.closeIconTestId); 40 | await closeButton.click(); 41 | } 42 | 43 | async checkSBOMpopup(page: Page, row: Locator): Promise { 44 | const sbomButton = row.getByTestId(TektonPO.sbomIconTestId); 45 | await sbomButton.click(); 46 | 47 | const searchBox = page.getByRole('textbox', { name: TektonPO.searchBoxName }); 48 | await searchBox.fill(TektonPO.sbomStepName); 49 | 50 | const span = page.getByText(TektonPO.sbomStepName); 51 | await expect(span).toBeVisible(); 52 | 53 | // Close popup 54 | const closeButton = page.getByRole('dialog').getByTestId(CommonPO.closeIconTestId); 55 | await closeButton.click(); 56 | } 57 | 58 | private async checkGraph(page: Page, row: Locator): Promise { 59 | const expandButton = row.getByRole('button', { name: TektonPO.expandButtonName }); 60 | const graph = page.locator(TektonPO.graphSelector); 61 | 62 | // Expand the row 63 | await expandButton.click(); 64 | 65 | // Check the graph is visible 66 | await expect(graph).toBeVisible(); 67 | 68 | // Fit to screen 69 | await page.getByRole('button', { name: TektonPO.fitToScreenButtonName }).click(); 70 | 71 | // Check all the tasks are visible 72 | for (const taskName of TektonPO.sourceTasks) { 73 | const task = page.locator(`g[data-test="task ${taskName}"]`); 74 | await expect(task).toBeVisible(); 75 | } 76 | 77 | // Check the graph buttons are visible 78 | for(const buttonName of [TektonPO.zoomInButtonName, TektonPO.zoomOutButtonName, TektonPO.fitToScreenButtonName, TektonPO.resetViewButtonName]) { 79 | const button = page.getByRole('button', { name: buttonName }); 80 | await expect(button).toBeVisible(); 81 | } 82 | 83 | // Collapse the row 84 | await expandButton.click(); 85 | 86 | await expect(graph).not.toBeVisible(); 87 | } 88 | 89 | async checkPipelineRunsTable(page: Page): Promise { 90 | // Wait for the Pipeline Runs section to be visible 91 | await expect(page.getByRole('heading', { name: /pipeline runs/i })).toBeVisible(); 92 | 93 | // Find the table and on-push row 94 | const table = page.locator('table').filter({ has: page.getByRole('columnheader', { name: 'NAME' }) }); 95 | const firstRow = table.locator('tbody tr').filter({ hasText: TektonPO.onPushRowRegex }).first(); 96 | await expect(firstRow).toBeVisible(); 97 | 98 | // 1. Shield icon next to name (look for shield icon with specific path, not the expand arrow) 99 | const shieldIcon = firstRow.locator('.signed-indicator svg'); 100 | await expect(shieldIcon).toBeVisible(); 101 | 102 | // 2. Vulnerabilities are shown (look for vulnerability severity levels) 103 | await expect(firstRow.getByRole('cell').filter({ hasText: TektonPO.vulnerabilitySeverityRegex }).first()).toBeVisible(); 104 | 105 | // 3. Status is Succeeded and has a tick 106 | await expect(firstRow).toContainText(CiPo.statusSucceededText); 107 | await expect(firstRow.locator(`[data-testid="${CiPo.statusOkTestId}"]`)).toBeVisible(); 108 | 109 | // 4. Started column has a date and time format (look for date pattern in any cell) 110 | await expect(firstRow.getByRole('cell').filter({ hasText: /\d{1,2}\/\d{1,2}\/\d{4}/ })).toBeVisible(); 111 | 112 | // 5. Task status has a visible bar (look for progress elements) 113 | // Skipping until https://redhat.atlassian.net/browse/SSCUI-82 is fixed 114 | //await expect(firstRow.locator('[role="progressbar"], [class*="bar"], [data-testid*="progress"]').first()).toBeVisible(); 115 | 116 | // 6. Duration is visible (e.g. `3 minutes 20 seconds`, `3 minutes`, or `45 seconds`) 117 | await expect(firstRow.getByRole('cell').filter({ hasText: TektonPO.durationRegex })).toBeVisible(); 118 | } 119 | 120 | async checkActions(page: Page): Promise { 121 | // Find the Pipeline Runs table specifically to avoid conflicts with other tables (e.g., ArgoCD) 122 | const pipelineRunsTable = page.locator('table').filter({ has: page.getByRole('columnheader', { name: 'NAME' }) }); 123 | 124 | // Scroll to the action column header within the Pipeline Runs table > 125 | await pipelineRunsTable.getByRole('columnheader', { name: TektonPO.actionsColumnHeader, exact: true }).scrollIntoViewIfNeeded(); | ^ Error: locator.scrollIntoViewIfNeeded: Test ended. 126 | 127 | const onPushRow = pipelineRunsTable.locator('tr').filter({ hasText: TektonPO.onPushRowRegex }).first(); 128 | 129 | await this.checkActionButtons(onPushRow); 130 | await this.checkLogsPopup(page, onPushRow); 131 | await this.checkSBOMpopup(page, onPushRow); 132 | await this.checkViewOutputPopup(page, onPushRow); 133 | await this.checkGraph(page, onPushRow); 134 | } 135 | 136 | /** 137 | * Verifies that registry links in both Image Scan and Image Check tabs 138 | * are actual clickable links that lead to an external registry (outside Developer Hub). 139 | * 140 | * This method: 141 | * 1. Opens the "View Output" dialog for an on-push pipeline row 142 | * 2. Checks Image Scan tab - verifies the image link is a real link (not just text) 143 | * 3. Checks Image Check tab - verifies the image link is a real link (not just text) 144 | * 4. Confirms links open in new tabs and navigate outside Developer Hub 145 | * 146 | * @param page - Playwright Page object 147 | */ 148 | public async checkImageRegistryLinks(page: Page): Promise { 149 | // Find the Pipeline Runs table (same pattern as checkActions) 150 | const pipelineRunsTable = page.locator('table').filter({ has: page.getByRole('columnheader', { name: 'NAME' }) }); 151 | 152 | // Scroll to the action column header 153 | await pipelineRunsTable.getByRole('columnheader', { name: TektonPO.actionsColumnHeader, exact: true }).scrollIntoViewIfNeeded(); 154 | 155 | // Find the on-push row within the table 156 | const onPushRow = pipelineRunsTable.locator('tr').filter({ hasText: TektonPO.onPushRowRegex }).first(); 157 | await expect(onPushRow).toBeVisible(); 158 | 159 | // Click "View Output" button 160 | const viewOutputButton = onPushRow.getByTestId(CommonPO.viewOutputIconTestId); 161 | await viewOutputButton.click(); 162 | 163 | const dialog = page.getByRole('dialog'); 164 | await expect(dialog).toBeVisible(); 165 | 166 | const closeButton = dialog.getByTestId(CommonPO.closeIconTestId); 167 | await closeButton.click(); 168 | } 169 | } 170 | ```