1+ import { Page , expect } from "@playwright/test" ;
2+
3+ /**
4+ * Navigation test helper functions for Canonical.com e2e tests
5+ */
6+
7+ export interface NavigationItem {
8+ title : string ;
9+ url ?: string ;
10+ selector ?: string ;
11+ hasDropdown ?: boolean ;
12+ }
13+
14+ export interface ViewportSize {
15+ width : number ;
16+ height : number ;
17+ name : string ;
18+ }
19+
20+ // Vanilla viewport sizes for responsive testing
21+ // https://vanillaframework.io/docs/settings/breakpoint-settings
22+ export const VIEWPORTS : ViewportSize [ ] = [
23+ { width : 1920 , height : 1080 , name : "Desktop Large" } ,
24+ { width : 1681 , height : 768 , name : "Desktop Standard" } ,
25+ { width : 1036 , height : 620 , name : "Tablet Landscape" } ,
26+ { width : 620 , height : 1024 , name : "Tablet Portrait" } ,
27+ { width : 460 , height : 667 , name : "Mobile" } ,
28+ ] ;
29+
30+ // Primary navigation items based on navigation.yaml analysis
31+ export const PRIMARY_NAV_ITEMS : NavigationItem [ ] = [
32+ { title : "Products" , selector : "#products-nav" , hasDropdown : true } ,
33+ { title : "Solutions" , selector : "#solutions-nav" , hasDropdown : true } ,
34+ { title : "Partners" , selector : "#partners-nav" , hasDropdown : true } ,
35+ { title : "Careers" , selector : "#careers-nav" , hasDropdown : true } ,
36+ { title : "Company" , selector : "#company-nav" , hasDropdown : true }
37+ ] ;
38+
39+ export const MOBILE_THRESHOLD = 1035 ;
40+
41+ /**
42+ * Check if an element exists and is visible on the page
43+ */
44+ export const isElementVisible = async ( page : Page , selector : string ) : Promise < boolean > => {
45+ try {
46+ const element = page . locator ( selector ) ;
47+ return await element . isVisible ( ) ;
48+ } catch {
49+ return false ;
50+ }
51+ } ;
52+
53+ /**
54+ * Accept cookie policy if present
55+ */
56+ export const acceptCookiePolicy = async ( page : Page ) : Promise < void > => {
57+ if ( await isElementVisible ( page , '#cookie-policy-button-accept-all' ) ) {
58+ await page . locator ( '#cookie-policy-button-accept-all' ) . click ( ) ;
59+ // Wait for cookie banner to disappear
60+ await expect ( page . locator ( '#cookie-policy-button-accept-all' ) ) . not . toBeVisible ( ) ;
61+ }
62+ } ;
63+
64+ /**
65+ * Check if we're on mobile viewport (width < MOBILE_THRESHOLD)
66+ */
67+ export const isMobileViewport = async ( page : Page ) : Promise < boolean > => {
68+ const viewport = page . viewportSize ( ) ;
69+ return viewport ? viewport . width < MOBILE_THRESHOLD : false ;
70+ } ;
71+
72+ /**
73+ * Open mobile menu if on mobile viewport
74+ */
75+ export const toggleMobileMenuIfNeeded = async ( page : Page ) : Promise < void > => {
76+ if ( await isMobileViewport ( page ) ) {
77+ const menuButton = page . locator ( '.js-menu-button' ) ;
78+ if ( await menuButton . isVisible ( ) ) {
79+ await menuButton . click ( ) ;
80+ }
81+ }
82+ } ;
83+
84+ /**
85+ * Click on a navigation item
86+ */
87+ export const clickNavigationItem = async ( page : Page , selector : string ) : Promise < void > => {
88+ const navItem = page . locator ( selector ) ;
89+ await navItem . click ( ) ;
90+ } ;
91+
92+ /**
93+ * Check if dropdown is open for a navigation item
94+ */
95+ export const isDropdownOpen = async ( page : Page , navItemId : string ) : Promise < boolean > => {
96+ const dropdownContent = page . locator ( `#${ navItemId . replace ( '-nav' , '-content' ) } ` ) . first ( ) ;
97+ return await dropdownContent . isVisible ( ) ;
98+ } ;
99+
100+ /**
101+ * Get all visible links in a dropdown
102+ */
103+ export const getDropdownLinks = async ( page : Page , navItemId : string ) : Promise < string [ ] > => {
104+ const dropdownContent = page . locator ( `#${ navItemId . replace ( '-nav' , '-content' ) } ` ) ;
105+ const links = dropdownContent . locator ( 'a[href]' ) ;
106+ const linkTexts : string [ ] = [ ] ;
107+
108+ const count = await links . count ( ) ;
109+ for ( let i = 0 ; i < count ; i ++ ) {
110+ const text = await links . nth ( i ) . textContent ( ) ;
111+ if ( text ?. trim ( ) ) {
112+ linkTexts . push ( text . trim ( ) ) ;
113+ }
114+ }
115+
116+ return linkTexts ;
117+ } ;
118+
119+ /**
120+ * Test search functionality
121+ */
122+ export const clickSearchButton = async ( page : Page ) : Promise < void > => {
123+ const searchButton = page . locator ( ( await isMobileViewport ( page ) ) ? '#js-search-button-mobile' : '#js-search-button-desktop' ) ;
124+ await searchButton . click ( ) ;
125+ } ;
126+
127+ export const testSearchFunctionality = async ( page : Page , searchTerm : string = "ubuntu" ) : Promise < void > => {
128+ // Open search
129+ await clickSearchButton ( page ) ;
130+
131+ // Wait for search input to be visible
132+ await page . waitForSelector ( '#navigation-search' , { state : 'visible' } ) ;
133+
134+ // Type search term
135+ await page . fill ( '#navigation-search' , searchTerm ) ;
136+
137+ // Submit search
138+ const searchButton = page . locator ( '.p-search-box__button' ) ;
139+ await searchButton . click ( ) ;
140+
141+ // Wait for navigation to search results
142+ await page . waitForURL ( '**/search**' ) ;
143+ } ;
144+
145+ /**
146+ * Check if secondary navigation is present
147+ */
148+ export const hasSecondaryNavigation = async ( page : Page ) : Promise < boolean > => {
149+ return await isElementVisible ( page , '#secondary-navigation' ) ;
150+ } ;
151+
152+ /**
153+ * Get secondary navigation items
154+ */
155+ export const getSecondaryNavigationItems = async ( page : Page ) : Promise < string [ ] > => {
156+ if ( ! ( await hasSecondaryNavigation ( page ) ) ) {
157+ return [ ] ;
158+ }
159+
160+ const secondaryNav = page . locator ( '#secondary-navigation .p-navigation__items a' ) ;
161+ const items : string [ ] = [ ] ;
162+
163+ const count = await secondaryNav . count ( ) ;
164+ for ( let i = 0 ; i < count ; i ++ ) {
165+ const text = await secondaryNav . nth ( i ) . textContent ( ) ;
166+ if ( text ?. trim ( ) ) {
167+ items . push ( text . trim ( ) ) ;
168+ }
169+ }
170+
171+ return items ;
172+ } ;
173+
174+ /**
175+ * Navigate to homepage and ensure clean state
176+ */
177+ export const navigateToHomepage = async ( page : Page ) : Promise < void > => {
178+ await page . goto ( '/' ) ;
179+ await acceptCookiePolicy ( page ) ;
180+ } ;
181+
182+ /**
183+ * Check if page has loaded successfully
184+ */
185+ export const verifyPageLoad = async ( page : Page , expectedUrl ?: string ) : Promise < void > => {
186+ // Check that page has loaded
187+ await page . waitForLoadState ( 'domcontentloaded' ) ;
188+
189+ // Verify URL if provided
190+ if ( expectedUrl ) {
191+ await expect ( page ) . toHaveURL ( new RegExp ( expectedUrl ) ) ;
192+ }
193+
194+ // Check that main content is visible
195+ await expect ( page . locator ( 'main, .main-content, body' ) ) . toBeVisible ( ) ;
196+ } ;
0 commit comments