# 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 30000ms exceeded. ``` ``` Error: expect(locator).toBeVisible() failed Locator: getByRole('table').filter({ has: getByRole('columnheader', { name: 'Pipeline Run ID' }) }).getByRole('columnheader', { name: 'Pipeline Run ID' }) Expected: visible Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 60000ms - waiting for getByRole('table').filter({ has: getByRole('columnheader', { name: 'Pipeline Run ID' }) }).getByRole('columnheader', { name: 'Pipeline Run ID' }) ``` # Test source ```ts 1 | import { escapeRegex } from '../../../utils/util'; 2 | import { CiPo } from '../../page-objects/ciPo'; 3 | import { CommonPO } from '../../page-objects/commonPo'; 4 | import { CIPlugin } from './ciPlugin'; 5 | import { expect, Locator, Page } from '@playwright/test'; 6 | 7 | export class BaseCIPlugin implements CIPlugin { 8 | protected name: string; 9 | protected imageUrlRegex: RegExp; 10 | 11 | constructor(name: string, registryOrg: string) { 12 | this.name = name; 13 | this.imageUrlRegex = new RegExp(`^${escapeRegex(registryOrg)}/`, 'i'); 14 | } 15 | 16 | public async checkCIHeading(page: Page): Promise { 17 | // For GitHub Actions, the name appears as a tab in Security Information, not a heading 18 | const heading = page.getByRole('heading', { name: this.name }); 19 | const tab = page.getByRole('tab', { name: this.name }); 20 | 21 | // Check if either heading or tab is visible 22 | const headingVisible = await heading.isVisible().catch(() => false); 23 | const tabVisible = await tab.isVisible().catch(() => false); 24 | 25 | if (!headingVisible && !tabVisible) { 26 | // Fallback: check for "Github Actions" tab (different casing) 27 | const tabAlt = page.getByRole('tab', { name: 'Github Actions' }); 28 | await expect(tabAlt).toBeVisible({ timeout: 10000 }); 29 | } 30 | } 31 | 32 | protected async checkViewOutputPopup(page: Page, row: Locator): Promise { 33 | const viewOutputButton = row.getByTestId(CommonPO.viewOutputIconTestId); 34 | await viewOutputButton.click(); 35 | 36 | const dialog = page.getByRole('dialog'); 37 | await expect(dialog).toBeVisible(); 38 | 39 | // Check results table 40 | const resultsTable = dialog.getByTestId(CiPo.resultsTableTestId); 41 | await expect(resultsTable).toBeVisible(); 42 | 43 | // Check column headers 44 | for (const column of CiPo.resultsTableColumns) { 45 | await expect(resultsTable.getByRole('columnheader', { name: column })).toBeVisible(); 46 | } 47 | 48 | // Check expected rows exist 49 | for (const rowName of CiPo.resultsTableRows) { 50 | await expect(resultsTable.getByRole('gridcell', { name: rowName, exact: true })).toBeVisible(); 51 | } 52 | 53 | // Check IMAGE_URL contains component name 54 | const imageUrlRow = resultsTable.getByRole('row').filter({ hasText: CiPo.imageUrlRow }); 55 | await expect(imageUrlRow).toContainText(this.name); 56 | 57 | // Check CHAINS-GIT_URL has a link 58 | const gitUrlRow = resultsTable.getByRole('row').filter({ hasText: CiPo.chainsGitUrlRow }); 59 | await expect(gitUrlRow.getByRole('link')).toBeVisible(); 60 | 61 | const closeButton = dialog.getByTestId(CommonPO.closeIconTestId); 62 | await closeButton.click(); 63 | } 64 | 65 | // eslint-disable-next-line no-unused-vars 66 | public async checkImageRegistryLinks(_page: Page): Promise { 67 | } 68 | 69 | // eslint-disable-next-line no-unused-vars 70 | public async checkActions(_page: Page): Promise { 71 | } 72 | 73 | // eslint-disable-next-line no-unused-vars 74 | public async checkPipelineRunsTable(_page: Page): Promise { 75 | } 76 | 77 | public async checkSecurityInformation(page: Page): Promise { 78 | // Security Information viewer may not be present for all CI providers 79 | const heading = page.getByRole('heading', { name: CiPo.securityInformationHeading }); 80 | const isVisible = await heading.isVisible().catch(() => false); 81 | if (!isVisible) { 82 | return; 83 | } 84 | 85 | // Scope column header checks to the security table 86 | const securityTable = page.getByRole('table').filter({ has: page.getByRole('columnheader', { name: CiPo.securityTableColumns[0] }) }); 87 | for (const column of CiPo.securityTableColumns) { > 88 | await expect(securityTable.getByRole('columnheader', { name: column })).toBeVisible(); | ^ Error: expect(locator).toBeVisible() failed 89 | } 90 | } 91 | } 92 | ```