# 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/gitopsResource.test.ts >> Gitops Resource UI Test Suite >> Verify CI - Location: tests/ui/gitopsResource.test.ts:63:3 # Error details ``` Error: expect(locator).toBeAttached() failed Locator: getByTestId('sidebar-root') Expected: attached Timeout: 30000ms Error: element(s) not found Call log: - Expect "toBeAttached" with timeout 30000ms - waiting for getByTestId('sidebar-root') ``` # Page snapshot ```yaml - main [ref=e3]: - heading "Red Hat Developer Hub" [level=1] [ref=e6] - article [ref=e7]: - heading "Select a sign-in method" [level=2] [ref=e10] - list [ref=e11]: - listitem [ref=e12]: - generic [ref=e13]: - generic [ref=e16]: OIDC - separator [ref=e17] - paragraph [ref=e19]: Sign in using OIDC - button "Sign In" [ref=e21] [cursor=pointer]: - generic [ref=e22]: Sign In ``` # Test source ```ts 1 | import { expect, Page, Locator } from '@playwright/test'; 2 | import { LoggerFactory } from '../logger/logger'; 3 | import type { Logger } from '../logger/logger'; 4 | 5 | const logger: Logger = LoggerFactory.getLogger('ui.common'); 6 | 7 | /** 8 | * Checks if a website URL returns an expected status code 9 | * @param page - The Playwright page object 10 | * @param href - The URL to check 11 | * @param expectedStatus - The expected HTTP status code (defaults to 200) 12 | */ 13 | export async function checkWebsiteStatus( 14 | page: Page, 15 | href: string, 16 | okStatuses: number[] = [200, 204, 301, 302, 307, 308] 17 | ): Promise { 18 | const response = await page.request.head(href); 19 | expect(okStatuses).toContain(response.status()); 20 | } 21 | 22 | /** 23 | * Hides the Quick start side panel if it is visible 24 | * @param page - The Playwright page object 25 | */ 26 | export async function hideQuickStartIfVisible(page: Page): Promise { 27 | // Wait for the page to be loaded by checking the self service icon 28 | const selfServiceIcon = page.getByTestId('AddCircleOutlineIcon'); 29 | await selfServiceIcon.waitFor({ state: 'visible', timeout: 20000 }); 30 | 31 | // Wait for welcome paragraph to be visible 32 | const welcomeParagraph = page.getByText("Let's get you started with Developer Hub", { exact: true }); 33 | const hideButton = page.getByRole('button', { name: 'Hide' }); 34 | try { 35 | await welcomeParagraph.waitFor({ state: 'visible', timeout: 2000 }); 36 | await hideButton.click({ timeout: 2000 }); 37 | logger.debug('Paragraph visible; hiding Quick start side panel'); 38 | } catch { 39 | logger.debug('Paragraph not visible; skipping hide'); 40 | } 41 | 42 | await expect(welcomeParagraph).toBeHidden({ timeout: 10000 }); 43 | } 44 | 45 | export async function waitForPageLoad(page: Page, name: string) { 46 | const progressBars = page.getByRole('main').getByRole('progressbar'); 47 | // Get all progressbar elements and wait until all are hidden 48 | const bars = await progressBars.all(); 49 | await Promise.all( 50 | bars.map(bar => expect(bar).toBeHidden({ timeout: 90000 })) 51 | ); 52 | > 53 | await expect(page.getByTestId('sidebar-root')).toBeAttached({ timeout: 30000 }); | ^ Error: expect(locator).toBeAttached() failed 54 | 55 | await expect(page.getByRole('heading', { name: name }).first()).toBeVisible({ timeout: 20000 }); 56 | await page.waitForLoadState(); 57 | } 58 | 59 | export async function openTab(page: Page, tabName: string) { 60 | const tab = page.getByRole('tablist').getByText(tabName); 61 | await tab.click(); 62 | } 63 | 64 | /** 65 | * Applies a blur filter to the locator element 66 | * @param locator - Locator to blur 67 | */ 68 | export async function blurLocator(locator: Locator): Promise { 69 | await locator.evaluate(el => { (el as HTMLElement).style.filter = 'blur(5px)'; }); 70 | } 71 | ```