# 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: expect(locator).toBeVisible() failed Locator: locator('table').filter({ has: getByRole('columnheader', { name: 'NAME' }) }).locator('tr').filter({ hasText: /on-push/i }).first().getByTestId('view-logs-icon') Expected: visible Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 60000ms - waiting for locator('table').filter({ has: getByRole('columnheader', { name: 'NAME' }) }).locator('tr').filter({ hasText: /on-push/i }).first().getByTestId('view-logs-icon') ``` # 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=e116]: - generic [ref=e117]: - paragraph [ref=e118]: component — service - heading "e2e-tests-java-quarkus-mnizlslx Add to favorites" [level=1] [ref=e119]: - generic [ref=e120]: - generic "component:default/e2e-tests-java-quarkus-mnizlslx | service | Secure Supply Chain Example for Quarkus Java example with advanced continuous integration pipeline covering building, CVE scanning, security scanning, signatures, attestations, SLSA provenance and SBOM along with Gitops-based continuous deployment." [ref=e122]: e2e-tests-java-quarkus-mnizlslx - button "Add to favorites" [ref=e123] [cursor=pointer]: - img [ref=e126] - generic [ref=e128]: - generic [ref=e130]: - paragraph [ref=e131]: Owner - paragraph [ref=e132]: - link "user:guest" [ref=e133] [cursor=pointer]: - /url: /catalog/default/user/guest - generic "user:default/guest" [ref=e134]: - img [ref=e136] - text: user:guest - generic [ref=e139]: - paragraph [ref=e140]: Lifecycle - paragraph [ref=e141]: experimental - button "more" [ref=e142] [cursor=pointer]: - img [ref=e144] - tablist "tabs" [ref=e150]: - tab "Overview" [ref=e151] [cursor=pointer] - tab "Topology" [ref=e152] [cursor=pointer] - tab "CI" [selected] [ref=e153] [cursor=pointer] - tab "CD" [ref=e154] [cursor=pointer] - tab "Kubernetes" [ref=e155] [cursor=pointer] - tab "API" [ref=e156] [cursor=pointer] - tab "Dependencies" [ref=e157] [cursor=pointer] - tab "Docs" [ref=e158] [cursor=pointer] - article [ref=e160]: - generic [ref=e163]: - generic [ref=e167]: - generic [ref=e168]: - paragraph [ref=e169]: Cluster - generic [ref=e172]: - button "rhdh-cluster" [ref=e173] [cursor=pointer]: - paragraph [ref=e174]: rhdh-cluster - textbox: rhdh-cluster - img - generic [ref=e175]: - paragraph [ref=e176]: Status - generic [ref=e179]: - button "All" [ref=e180] [cursor=pointer]: - paragraph [ref=e181]: All - textbox: All - img - generic [ref=e182]: - generic "Collapse all" [ref=e183]: - button [disabled]: - generic: - img - generic "Expand all" [ref=e184]: - button [ref=e185] [cursor=pointer]: - img [ref=e187] - separator [ref=e189] - generic [ref=e192]: - generic [ref=e193]: - heading "Pipeline Runs" [level=2] [ref=e194] - generic "search" [ref=e196]: - img [ref=e198] - textbox "Search" [ref=e200] - generic [ref=e201]: - button "clear search" [disabled]: - generic: - img - table [ref=e202]: - rowgroup [ref=e203]: - row "NAME VULNERABILITIES STATUS TASK STATUS STARTED DURATION ACTIONS" [ref=e204]: - columnheader [ref=e205] - columnheader "NAME" [ref=e206]: - button "NAME" [ref=e207] [cursor=pointer]: - text: NAME - img [ref=e208] - columnheader "VULNERABILITIES" [ref=e210]: - button "VULNERABILITIES" [ref=e211] [cursor=pointer]: - text: VULNERABILITIES - img [ref=e212] - columnheader "STATUS" [ref=e214]: - button "STATUS" [ref=e215] [cursor=pointer]: - text: STATUS - img [ref=e216] - columnheader "TASK STATUS" [ref=e218]: - button "TASK STATUS" [ref=e219] [cursor=pointer]: - text: TASK STATUS - img [ref=e220] - columnheader "STARTED" [ref=e222]: - button "STARTED" [ref=e223] [cursor=pointer]: - text: STARTED - img [ref=e224] - columnheader "DURATION" [ref=e226]: - button "DURATION" [ref=e227] [cursor=pointer]: - text: DURATION - img [ref=e228] - columnheader "ACTIONS" [ref=e230] - rowgroup [ref=e231]: - row "No Pipeline Runs found" [ref=e232]: - cell "No Pipeline Runs found" [ref=e233]: - generic [ref=e234]: No Pipeline Runs found ``` # Test source ```ts 1 | import { expect, Page, Locator } from '@playwright/test'; 2 | import { BaseCIPlugin } from './baseCIPlugin'; 3 | import { TektonPO } from '../../page-objects/tektonPo'; 4 | import { CiPo } from '../../page-objects/ciPo'; 5 | import { CommonPO } from '../../page-objects/commonPo'; 6 | 7 | export class TektonPlugin extends BaseCIPlugin { 8 | constructor(name: string, registryOrg: string) { 9 | super(name, registryOrg); 10 | } 11 | 12 | private async checkActionButtons(onPushRow: Locator): Promise { 13 | for (const testId of [TektonPO.logsIconTestId, TektonPO.sbomIconTestId, TektonPO.viewOutputTestId]) { 14 | const button = onPushRow.getByTestId(testId); > 15 | await expect(button).toBeVisible(); | ^ Error: expect(locator).toBeVisible() failed 16 | } 17 | } 18 | 19 | private async checkLogsPopup(page: Page, row: Locator): Promise { 20 | const logsButton = row.getByTestId(TektonPO.logsIconTestId); 21 | await logsButton.click(); 22 | 23 | const logsPopup = page.getByTitle(TektonPO.logsDialogTitle); 24 | await expect(logsPopup).toBeVisible(); 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 | ```