# 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: 'ID', exact: true }) }).locator('tbody tr[index]').first().getByRole('button', { name: 'View Logs' }) Expected: visible Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 60000ms - waiting for locator('table').filter({ has: getByRole('columnheader', { name: 'ID', exact: true }) }).locator('tbody tr[index]').first().getByRole('button', { name: 'View Logs' }) ``` # 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-nodejs-apjgsfwx Add to favorites" [level=1] [ref=e119]: - generic [ref=e120]: - generic "component:default/e2e-tests-nodejs-apjgsfwx | service | Secure Supply Chain Example for Node.js+Express REST API 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-nodejs-apjgsfwx - 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=e161]: - alert [ref=e164]: - 'button "Error: Request failed with 504 Gateway Time-out" [ref=e165] [cursor=pointer]': - generic [ref=e166]: - img [ref=e167] - 'heading "Error: Request failed with 504 Gateway Time-out" [level=6] [ref=e169]' - img [ref=e172] - generic [ref=e177]: - heading "Security Information" [level=4] [ref=e180] - generic [ref=e182]: - tablist "Multi CI" [ref=e185]: - tab "Azure Pipelines" [selected] [ref=e186] [cursor=pointer]: - generic [ref=e187]: Azure Pipelines - generic [ref=e191]: - generic [ref=e192]: - generic [ref=e195]: - generic [ref=e197]: - img [ref=e199] - button "Name" [ref=e201] [cursor=pointer] - textbox: Name - img - generic "search" [ref=e203]: - img [ref=e205] - textbox "Search by name" [ref=e207] - generic [ref=e209]: - generic "rows per page" [ref=e210]: - button "1 - 0 of 0" [ref=e211] [cursor=pointer] - textbox: "5" - img - button "first page" [disabled]: - generic: - img - button "previous page" [disabled]: - generic: - img - generic "page number" [ref=e212]: - generic [ref=e213]: - textbox [ref=e214]: "1" - group - paragraph [ref=e215]: of 0 - button "next page" [ref=e216] [cursor=pointer]: - img [ref=e218] - button "last page" [ref=e220] [cursor=pointer]: - img [ref=e222] - progressbar [ref=e226]: - img [ref=e227] ``` # Test source ```ts 1 | import { expect, Locator, Page } from '@playwright/test'; 2 | import { BaseCIPlugin } from './baseCIPlugin'; 3 | import { checkWebsiteStatus } from '../../commonUi'; 4 | import { AzurePO } from '../../page-objects/azurePo'; 5 | import { CiPo } from '../../page-objects/ciPo'; 6 | import { CommonPO } from '../../page-objects/commonPo'; 7 | import { LoggerFactory, Logger } from '../../../logger/logger'; 8 | 9 | export class AzurePlugin extends BaseCIPlugin { 10 | private readonly logger: Logger = LoggerFactory.getLogger('AzurePlugin'); 11 | 12 | constructor(name: string, registryOrg: string) { 13 | super(name, registryOrg); 14 | } 15 | 16 | private getPprTable(page: Page): Locator { 17 | return page.locator('table').filter({ 18 | has: page.getByRole('columnheader', { name: AzurePO.columnHeaders[0], exact: true }) 19 | }); 20 | } 21 | 22 | private async checkColumnHeaders(table: Locator): Promise { 23 | for (const header of AzurePO.columnHeaders) { 24 | await expect(table.getByRole('columnheader', { name: header })).toBeVisible(); 25 | } 26 | } 27 | 28 | private async checkRowCellsVisible(cells: Locator[]): Promise { 29 | expect(cells).toHaveLength(AzurePO.columnHeaders.length); 30 | for (const cell of cells) { 31 | await expect(cell).toBeVisible(); 32 | } 33 | } 34 | 35 | private async checkRowCellContents(page: Page, cells: Locator[]): Promise { 36 | const { cellIndex } = AzurePO; 37 | 38 | await expect(cells[cellIndex.id]).toHaveText(AzurePO.pprNumberRegex); 39 | 40 | const pprLink = cells[cellIndex.build].getByRole('link'); 41 | await expect(pprLink).toBeVisible(); 42 | const pprLinkHref = await pprLink.getAttribute('href'); 43 | expect(pprLinkHref).not.toBeNull(); 44 | await checkWebsiteStatus(page, pprLinkHref!); 45 | 46 | await expect(cells[cellIndex.source]).toHaveText(AzurePO.branchCommitRegex); 47 | 48 | await expect(cells[cellIndex.state].getByTestId(CiPo.statusOkTestId)).toBeVisible(); 49 | await expect(cells[cellIndex.state]).toContainText(CiPo.statusSucceededText); 50 | 51 | await expect(cells[cellIndex.duration]).toHaveText(AzurePO.durationRegex); 52 | 53 | await expect(cells[cellIndex.age]).toHaveText(AzurePO.relativeTimeRegex); 54 | 55 | await expect(cells[cellIndex.logs].getByRole('button', { name: AzurePO.viewLogsButtonName })).toBeVisible(); 56 | } 57 | 58 | public async checkCIHeading(page: Page): Promise { 59 | await expect(page.getByRole('heading', { name: AzurePO.pipelinesHeading })).toBeVisible(); 60 | } 61 | 62 | public async checkActions(page: Page): Promise { 63 | const pipelineRunsTable = this.getPprTable(page); 64 | const firstRow = pipelineRunsTable.locator(CommonPO.dataRowSelector).first(); 65 | 66 | const viewLogsButton = firstRow.getByRole('button', { name: AzurePO.viewLogsButtonName }); > 67 | await expect(viewLogsButton).toBeVisible(); | ^ Error: expect(locator).toBeVisible() failed 68 | await viewLogsButton.click(); 69 | 70 | const logsPopup = page.getByRole('heading', { name: AzurePO.logsPopupHeadingRegex }); 71 | await expect(logsPopup).toBeVisible(); 72 | 73 | const closeButton = page.getByRole('button', { name: AzurePO.closeButtonName }); 74 | await closeButton.click(); 75 | } 76 | 77 | public async checkPipelineRunsTable(page: Page): Promise { 78 | const pipelineRunsTable = this.getPprTable(page); 79 | await expect(pipelineRunsTable).toBeVisible(); 80 | 81 | await this.checkColumnHeaders(pipelineRunsTable); 82 | 83 | const tableRows = pipelineRunsTable.locator(CommonPO.dataRowSelector); 84 | await expect(tableRows.first()).toBeVisible(); 85 | 86 | const tableRowCells = await tableRows.first().locator('td').all(); 87 | await this.checkRowCellsVisible(tableRowCells); 88 | await this.checkRowCellContents(page, tableRowCells); 89 | } 90 | 91 | // eslint-disable-next-line no-unused-vars 92 | public async checkImageRegistryLinks(_page: Page): Promise { 93 | this.logger.info('Skipping checkImageRegistryLinks - not applicable for Azure DevOps CI'); 94 | } 95 | } 96 | ```