|
1 | | -import { expect, Page, test } from '@playwright/test' |
| 1 | +import { expect, Page, Locator, test } from '@playwright/test' |
2 | 2 | import { step } from '../rancher/rancher-test' |
3 | 3 | import { RancherUI } from './rancher-ui' |
4 | 4 | import { RancherCommonPage } from '../rancher/rancher-common.page' |
5 | 5 |
|
6 | | -type ExpGroup = 'Cluster' | 'Workloads' | 'Apps' | 'Storage' | 'Admission Policy Management' | 'SBOMScanner' |
7 | | -type ExpItemMap = { |
| 6 | +// Explorer navigation |
| 7 | +type ENav = 'Cluster' | 'Workloads' | 'Apps' | 'Storage' | 'Admission Policy Management' | 'SBOMScanner' |
| 8 | +type ENavMap = { |
8 | 9 | 'Cluster' : 'Projects/Namespaces' | 'Nodes' | 'Cluster and Project Members' | 'Events' |
9 | 10 | 'Workloads' : 'CronJobs' | 'DaemonSets' | 'Deployments' | 'Jobs' | 'StatefulSets' | 'Pods' |
10 | 11 | 'Apps' : 'Charts' | 'Installed Apps' | 'Repositories' | 'Recent Operations' |
11 | 12 | 'Storage' : 'PersistentVolumes' | 'StorageClasses' | 'ConfigMaps' | 'PersistentVolumeClaims' | 'Secrets' |
12 | 13 | 'Admission Policy Management': 'Policy Servers' | 'Cluster Admission Policies' | 'Admission Policies' | 'Policy Reporter' |
13 | | - 'SBOMScanner' : 'Images' | 'Workloads Scan' | 'Registries configuration' | 'VEX Management' |
| 14 | + 'SBOMScanner' : 'Images' | 'Advanced' |
14 | 15 | } |
15 | | - |
| 16 | +// Expandable items in ENavMap that have a third navigation level |
| 17 | +type ENavSubMap = { |
| 18 | + SBOMScanner: { |
| 19 | + Advanced: 'Workloads Scan' | 'VEX Management' | 'Registries configuration' |
| 20 | + } |
| 21 | +} |
| 22 | +// Returns valid sub-items for a given group and child |
| 23 | +type ENavChild<T extends ENav, C extends ENavMap[T]> = |
| 24 | + T extends keyof ENavSubMap |
| 25 | + ? C extends keyof ENavSubMap[T] |
| 26 | + ? ENavSubMap[T][C] |
| 27 | + : never |
| 28 | + : never |
| 29 | + |
| 30 | +// Fleet navigation |
16 | 31 | // Rancher v2.12 renamed Advanced to Resources |
17 | | -type FleetGroup = '' | 'Advanced' | 'Resources' |
18 | | -type FleetItemMap = { |
19 | | - '' : 'Dashboard' | 'Git Repos' | 'App Bundles' | 'Clusters' | 'Cluster Groups' | 'Workspaces' |
20 | | - 'Advanced' : 'Workspaces' | 'BundleNamespaceMappings' | 'Bundles' | 'Cluster Registration Tokens' | 'GitRepoRestrictions' |
21 | | - 'Resources': 'Git Repos' | 'Helm Ops' | 'BundleNamespaceMappings' | 'Bundles' | 'Cluster Registration Tokens' | 'GitRepoRestrictions' |
| 32 | +type FNav = 'Dashboard' | 'Git Repos' | 'App Bundles' | 'Clusters' | 'Cluster Groups' | 'Workspaces' | 'Advanced' | 'Resources' |
| 33 | +type FNavMap = { |
| 34 | + Advanced : 'Workspaces' | 'BundleNamespaceMappings' | 'Bundles' | 'Cluster Registration Tokens' | 'GitRepoRestrictions' |
| 35 | + Resources: 'Git Repos' | 'Helm Ops' | 'BundleNamespaceMappings' | 'Bundles' | 'Cluster Registration Tokens' | 'GitRepoRestrictions' |
22 | 36 | } |
| 37 | +type FNavChild<T extends FNav> = T extends keyof FNavMap ? FNavMap[T] : never |
23 | 38 |
|
24 | 39 | export interface Cluster { |
25 | 40 | id : string |
@@ -55,42 +70,46 @@ export class Navigation { |
55 | 70 | }, 'User menu occasionally does not open', { reload: false }) |
56 | 71 | } |
57 | 72 |
|
58 | | - private async sideNavHandler(groupName: string, childName?: string) { |
59 | | - const groupHeader = this.page.getByRole('heading', { name: groupName, exact: true }) |
60 | | - let groupBlock = this.page.locator('nav.side-nav').locator('.accordion') |
61 | | - if (groupName) groupBlock = groupBlock.filter({ has: groupHeader }) |
62 | | - |
63 | | - // Expand group if needed |
64 | | - if (groupName && childName) { |
65 | | - const expandBtn = groupBlock.locator('i.icon-chevron-down,i.icon-chevron-right') |
66 | | - // Can't detect with expandBtn.isVisible, conflict in: icon-down = closed (2.7) = open (2.8) |
67 | | - await expect(groupBlock).toBeVisible() |
68 | | - if (!await groupBlock.getByRole('list').first().isVisible()) { |
69 | | - await expandBtn.click() |
| 73 | + private async sideNavHandler(groupName: string, childName?: string, subChildName?: string) { |
| 74 | + const expand = async(block: Locator) => { |
| 75 | + await expect(block).toBeVisible() |
| 76 | + if (!await block.getByRole('list').first().isVisible()) { |
| 77 | + await block.locator('i.icon-chevron-right').click() |
70 | 78 | } |
71 | 79 | } |
72 | | - // Click menu item |
73 | | - if (childName) { |
74 | | - await groupBlock.getByText(childName, { exact: true }).click() |
75 | | - } else { |
76 | | - await groupBlock.locator(groupHeader).click() |
| 80 | + const groupBlock = this.page.locator('nav.side-nav .accordion', { has: this.page.getByText(groupName, { exact: true }) }) |
| 81 | + |
| 82 | + if (!childName) { |
| 83 | + await groupBlock.getByText(groupName, { exact: true }).click() |
| 84 | + return |
77 | 85 | } |
78 | 86 |
|
79 | | - // 2nd level children not implemented |
| 87 | + await expand(groupBlock) |
| 88 | + const childBlock = groupBlock.getByRole('listitem').filter({ has: this.page.getByText(childName, { exact: true }) }) |
| 89 | + |
| 90 | + if (subChildName) await expand(childBlock) |
| 91 | + await childBlock.getByText(subChildName || childName, { exact: true }).click() |
80 | 92 | } |
81 | 93 |
|
82 | 94 | @step |
83 | | - async fleet<T extends FleetGroup>(groupName?: T, childName?: FleetItemMap[T]) { |
| 95 | + async fleet<T extends FNav>(groupName?: T, childName?: FNavChild<T>) { |
84 | 96 | await this.mainNav('Continuous Delivery') |
85 | | - if (groupName !== undefined) await this.sideNavHandler(groupName, childName) |
| 97 | + if (!groupName) return |
| 98 | + |
| 99 | + // Backwards compatibility overrides |
| 100 | + if (RancherUI.isVersion('<=2.11')) { |
| 101 | + if (childName == 'Git Repos') return await this.sideNavHandler('Git Repos') |
| 102 | + if (groupName == 'Resources') return await this.sideNavHandler('Advanced', childName) |
| 103 | + } |
| 104 | + await this.sideNavHandler(groupName, childName) |
86 | 105 | } |
87 | 106 |
|
88 | 107 | @step |
89 | | - async explorer<T extends ExpGroup>(groupName: T, childName?: ExpItemMap[T]) { |
| 108 | + async explorer<T extends ENav, C extends ENavMap[T]>(groupName: T, childName?: C, subChildName?: ENavChild<T, C>) { |
90 | 109 | if (this.isblank()) await this.cluster() |
91 | | - await this.sideNavHandler(groupName, childName) |
| 110 | + await this.sideNavHandler(groupName, childName, subChildName) |
92 | 111 |
|
93 | | - // Handle known cases of empty tables |
| 112 | + // Handle empty tables - https://github.com/rancher/rancher/issues/54281 |
94 | 113 | if (childName === 'Installed Apps' || childName === 'CronJobs') { |
95 | 114 | const row = this.ui.tableRow(/^(rancher|audit-scanner)$/).row |
96 | 115 | await expect(row).toBeVisible().catch(async() => { |
@@ -136,8 +155,8 @@ export class Navigation { |
136 | 155 | // ================================================================================================== |
137 | 156 | // Kubewarden specific helpers |
138 | 157 |
|
139 | | - @step // Overview |
140 | | - async kubewarden(childName?: ExpItemMap['Admission Policy Management']) { |
| 158 | + @step // Dashboard |
| 159 | + async kubewarden(childName?: ENavMap['Admission Policy Management']) { |
141 | 160 | await this.explorer('Admission Policy Management', childName) |
142 | 161 | } |
143 | 162 |
|
@@ -169,16 +188,13 @@ export class Navigation { |
169 | 188 | // SBOMScanner specific helpers |
170 | 189 |
|
171 | 190 | @step |
172 | | - async sbomScanner(childName?: ExpItemMap['SBOMScanner']) { |
173 | | - // Navigation does not support 2nd level items (Advanced > Registries) |
174 | | - if (childName === 'VEX Management') |
175 | | - await this.goto(`dashboard/c/local/imageScanner/sbomscanner.kubewarden.io.vexhub`) |
176 | | - else if (childName === 'Workloads Scan') |
177 | | - await this.goto(`dashboard/c/local/imageScanner/sbomscanner.kubewarden.io.workloadscanconfiguration`) |
178 | | - else if (childName === 'Registries configuration') |
179 | | - await this.goto(`dashboard/c/local/imageScanner/sbomscanner.kubewarden.io.registry`) |
180 | | - else |
| 191 | + async sbomScanner(childName?: ENavMap['SBOMScanner'] | ENavChild<'SBOMScanner', ENavMap['SBOMScanner']>) { |
| 192 | + // Shortcut to Advanced sub-items |
| 193 | + if (childName === 'VEX Management' || childName === 'Workloads Scan' || childName === 'Registries configuration') { |
| 194 | + await this.explorer('SBOMScanner', 'Advanced', childName) |
| 195 | + } else { |
181 | 196 | await this.explorer('SBOMScanner', childName) |
| 197 | + } |
182 | 198 |
|
183 | 199 | const heading = childName || /^(Dashboard|Install SBOMScanner)/ |
184 | 200 | await expect(this.page.locator('div.title').getByText(heading)).toBeVisible() |
|
0 commit comments