Skip to content

Commit a0a0acc

Browse files
committed
Add navigation helpers on breadcrumbs e2e component test to stop flaky behavior on webkit
1 parent f9c68a3 commit a0a0acc

3 files changed

Lines changed: 93 additions & 44 deletions

File tree

‎test/e2e/helpers/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export { clearConsentCookies } from '@test/e2e/helpers/browserState'
1515
export { BasePage } from '@test/e2e/helpers/pageObjectModels/BasePage'
1616
export { ComponentPersistencePage } from '@test/e2e/helpers/pageObjectModels/ComponentPersistencePage'
1717
export { HeadPage } from '@test/e2e/helpers/pageObjectModels/HeadPage'
18+
export { BreadCrumbPage } from '@test/e2e/helpers/pageObjectModels/BreadCrumbPage'
1819
export {
1920
setupCleanTestPage,
2021
setupTestPage,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Breadcrumb-specific Page Object Model
3+
*/
4+
import type { Page } from '@playwright/test'
5+
import { TestError } from '@test/errors'
6+
import { BasePage, setupTestPage } from '@test/e2e/helpers'
7+
8+
export class BreadCrumbPage extends BasePage {
9+
protected constructor(page: Page) {
10+
super(page)
11+
}
12+
13+
static override async init(page: Page): Promise<BreadCrumbPage> {
14+
await page.addInitScript(() => {
15+
window.isPlaywrightControlled = true
16+
})
17+
const instance = new BreadCrumbPage(page)
18+
await instance.onInit()
19+
return instance
20+
}
21+
22+
async navigateToListingDetail(options: {
23+
listingPath: string
24+
linkSelector: string
25+
minSegments: number
26+
notFoundMessage: string
27+
}): Promise<void> {
28+
const { listingPath, linkSelector, minSegments, notFoundMessage } = options
29+
await setupTestPage(this.page, listingPath)
30+
31+
const targetHref = await this.evaluate(({ selector, segments }) => {
32+
const anchors = Array.from(document.querySelectorAll<HTMLAnchorElement>(selector))
33+
const candidate = anchors.find(anchor => {
34+
const href = anchor.getAttribute('href')
35+
if (!href || !href.startsWith('/')) {
36+
return false
37+
}
38+
const parts = href.split('/').filter(Boolean)
39+
return parts.length >= segments
40+
})
41+
return candidate?.getAttribute('href') ?? null
42+
}, { selector: linkSelector, segments: minSegments })
43+
44+
if (!targetHref) {
45+
throw new TestError(notFoundMessage)
46+
}
47+
48+
const waitForLoad = this.waitForPageLoad()
49+
await this.navigateToPage(targetHref)
50+
await waitForLoad
51+
}
52+
53+
async openFirstArticleDetail(): Promise<void> {
54+
await this.navigateToListingDetail({
55+
listingPath: '/articles',
56+
linkSelector: 'main a[href^="/articles/"]',
57+
minSegments: 2,
58+
notFoundMessage: 'Could not find article detail link on /articles',
59+
})
60+
}
61+
62+
async openFirstServiceDetail(): Promise<void> {
63+
await this.navigateToListingDetail({
64+
listingPath: '/services',
65+
linkSelector: 'main a[href^="/services/"]',
66+
minSegments: 2,
67+
notFoundMessage: 'Could not find service detail link on /services',
68+
})
69+
}
70+
}

‎test/e2e/specs/04-components/breadcrumbs.spec.ts‎

Lines changed: 22 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,29 @@
55
*/
66

77
import {
8-
BasePage,
8+
BreadCrumbPage,
99
expect,
10-
setupTestPage,
1110
test,
12-
} from '@test/e2e/helpers'
11+
} from '@test/e2e/helpers'
1312

1413
test.describe('Breadcrumbs Component', () => {
1514
test('@ready breadcrumbs display on article pages', async ({ page: playwrightPage }) => {
16-
const page = await BasePage.init(playwrightPage)
17-
await setupTestPage(playwrightPage, '/articles')
18-
await page.click('a[href*="/articles/"]')
19-
await page.waitForLoadState('networkidle')
15+
const page = await BreadCrumbPage.init(playwrightPage)
16+
await page.openFirstArticleDetail()
2017

2118
await page.expectElementVisible('nav[aria-label="Breadcrumb"]')
2219
})
2320

2421
test('@ready breadcrumbs display on service pages', async ({ page: playwrightPage }) => {
25-
const page = await BasePage.init(playwrightPage)
26-
await setupTestPage(playwrightPage, '/services')
27-
await page.click('a[href*="/services/"]')
28-
await page.waitForLoadState('networkidle')
22+
const page = await BreadCrumbPage.init(playwrightPage)
23+
await page.openFirstServiceDetail()
2924

3025
await page.expectElementVisible('nav[aria-label="Breadcrumb"]')
3126
})
3227

3328
test('@ready breadcrumbs show correct path', async ({ page: playwrightPage }) => {
34-
const page = await BasePage.init(playwrightPage)
35-
await setupTestPage(playwrightPage, '/articles')
36-
await page.click('a[href*="/articles/"]')
37-
await page.waitForLoadState('networkidle')
29+
const page = await BreadCrumbPage.init(playwrightPage)
30+
await page.openFirstArticleDetail()
3831

3932
const count = await page.countElements('nav[aria-label="Breadcrumb"] a')
4033
expect(count).toBeGreaterThan(0)
@@ -45,10 +38,8 @@ test.describe('Breadcrumbs Component', () => {
4538
})
4639

4740
test('@ready breadcrumb links are clickable', async ({ page: playwrightPage }) => {
48-
const page = await BasePage.init(playwrightPage)
49-
await setupTestPage(playwrightPage, '/articles')
50-
await page.click('a[href*="/articles/"]')
51-
await page.waitForLoadState('networkidle')
41+
const page = await BreadCrumbPage.init(playwrightPage)
42+
await page.openFirstArticleDetail()
5243

5344
await page.click('nav[aria-label="Breadcrumb"] a')
5445
await page.waitForLoadState('networkidle')
@@ -57,10 +48,8 @@ test.describe('Breadcrumbs Component', () => {
5748
})
5849

5950
test('@ready current page is not a link', async ({ page: playwrightPage }) => {
60-
const page = await BasePage.init(playwrightPage)
61-
await setupTestPage(playwrightPage, '/articles')
62-
await page.click('a[href*="/articles/"]')
63-
await page.waitForLoadState('networkidle')
51+
const page = await BreadCrumbPage.init(playwrightPage)
52+
await page.openFirstArticleDetail()
6453

6554
// Last item should have aria-current="page" on the span, not be a link
6655
await page.expectElementVisible('nav[aria-label="Breadcrumb"] li:last-child span[aria-current="page"]')
@@ -71,10 +60,8 @@ test.describe('Breadcrumbs Component', () => {
7160
})
7261

7362
test('@ready breadcrumbs have proper separators', async ({ page: playwrightPage }) => {
74-
const page = await BasePage.init(playwrightPage)
75-
await setupTestPage(playwrightPage, '/articles')
76-
await page.click('a[href*="/articles/"]')
77-
await page.waitForLoadState('networkidle')
63+
const page = await BreadCrumbPage.init(playwrightPage)
64+
await page.openFirstArticleDetail()
7865

7966
const itemCount = await page.countElements('nav[aria-label="Breadcrumb"] li')
8067
expect(itemCount).toBeGreaterThan(1)
@@ -85,10 +72,8 @@ test.describe('Breadcrumbs Component', () => {
8572
})
8673

8774
test('@ready breadcrumbs use proper ARIA', async ({ page: playwrightPage }) => {
88-
const page = await BasePage.init(playwrightPage)
89-
await setupTestPage(playwrightPage, '/articles')
90-
await page.click('a[href*="/articles/"]')
91-
await page.waitForLoadState('networkidle')
75+
const page = await BreadCrumbPage.init(playwrightPage)
76+
await page.openFirstArticleDetail()
9277

9378
await page.expectElementVisible('nav[aria-label="Breadcrumb"]')
9479

@@ -97,22 +82,17 @@ test.describe('Breadcrumbs Component', () => {
9782
})
9883

9984
test('@ready breadcrumbs are responsive', async ({ page: playwrightPage }) => {
100-
const page = await BasePage.init(playwrightPage)
85+
const page = await BreadCrumbPage.init(playwrightPage)
10186
await page.setViewport(375, 667)
102-
await setupTestPage(playwrightPage, '/articles')
103-
104-
await page.click('a[href*="/articles/"]', { force: true }) // Bypass cookie dialog overlay
105-
await page.waitForLoadState('networkidle')
87+
await page.openFirstArticleDetail()
10688

10789
await page.expectElementVisible('nav[aria-label="Breadcrumb"]')
10890
})
10991

11092
test('@ready breadcrumbs have structured data', async ({ page: playwrightPage }) => {
11193
// Expected: Should include JSON-LD BreadcrumbList schema
112-
const page = await BasePage.init(playwrightPage)
113-
await setupTestPage(playwrightPage, '/articles')
114-
await page.click('a[href*="/articles/"]')
115-
await page.waitForLoadState('networkidle')
94+
const page = await BreadCrumbPage.init(playwrightPage)
95+
await page.openFirstArticleDetail()
11696

11797
const jsonLd = await playwrightPage.locator('script[type="application/ld+json"]').allTextContents()
11898
const hasBreadcrumbSchema = jsonLd.some((json) => json.includes('BreadcrumbList'))
@@ -121,10 +101,8 @@ test.describe('Breadcrumbs Component', () => {
121101
})
122102

123103
test('@ready breadcrumbs truncate long titles', async ({ page: playwrightPage }) => {
124-
const page = await BasePage.init(playwrightPage)
125-
await setupTestPage(playwrightPage, '/articles')
126-
await page.click('a[href*="/articles/"]')
127-
await page.waitForLoadState('networkidle')
104+
const page = await BreadCrumbPage.init(playwrightPage)
105+
await page.openFirstArticleDetail()
128106

129107
const hasEllipsis = await playwrightPage.locator('nav[aria-label="Breadcrumb"] li').last().evaluate((el) => {
130108
const styles = window.getComputedStyle(el)

0 commit comments

Comments
 (0)