# 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-python-vsxrpqdl Add to favorites" [level=1] [ref=e119]: - generic [ref=e120]: - generic "component:default/e2e-tests-python-vsxrpqdl | service | Secure Supply Chain Example for Python is an interpreted, object-oriented, high-level programming language with dynamic semantics. This sample demonstrates software supply chain security functionalty using an 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-python-vsxrpqdl - 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 | ```