Skip to content

Commit 5bce8f8

Browse files
Expand visual regression tests
1 parent ecf1870 commit 5bce8f8

1 file changed

Lines changed: 195 additions & 85 deletions

File tree

test/visual/regression.e2e.ts

Lines changed: 195 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -19,106 +19,216 @@ import { expect, test } from '../e2e/utils'
1919
// set a fixed time to avoid diffs due to irrelevant time differences
2020
test.beforeEach(async ({ page }) => {
2121
await page.clock.setFixedTime(new Date('2025-10-23T12:34:56.000Z'))
22+
// TODO: revert to default viewport once we've confirmed no visual regressions
23+
// from the grid layout change. The tall viewport forces all content to render
24+
// without scrolling, so fullPage screenshots are comparable between the old
25+
// contained-scroll layout and the new document-scroll layout.
26+
await page.setViewportSize({ width: 1280, height: 3100 })
2227
})
2328

24-
test.describe('Visual Regression', { tag: '@visual' }, () => {
25-
test('projects list', async ({ page }) => {
26-
await page.goto('/projects')
27-
await expect(page.getByRole('heading', { name: 'Projects' })).toBeVisible()
28-
await expect(page).toHaveScreenshot('projects-list.png', { fullPage: true })
29-
})
30-
31-
test('instances list', async ({ page }) => {
32-
await page.goto('/projects/mock-project/instances')
33-
await expect(page.getByRole('heading', { name: 'Instances' })).toBeVisible()
34-
await expect(page).toHaveScreenshot('instances-list.png', { fullPage: true })
35-
})
36-
37-
test('instance detail', async ({ page }) => {
38-
await page.goto('/projects/mock-project/instances/db1')
39-
await expect(page.getByRole('heading', { name: 'db1' })).toBeVisible()
40-
await expect(page).toHaveScreenshot('instance-detail.png', { fullPage: true })
41-
})
42-
43-
test('create disk', async ({ page }) => {
44-
await page.goto('/projects/mock-project/disks-new')
45-
await expect(page.getByRole('heading', { name: 'Create disk' })).toBeVisible()
46-
await expect(page).toHaveScreenshot('disks-new.png', { fullPage: true })
47-
})
29+
const fullPage = { fullPage: true }
30+
31+
const p = '/projects/mock-project'
32+
33+
// Standard pages: goto URL, wait for heading, take full-page screenshot
34+
const pages = [
35+
// Auth
36+
{ name: 'device verify', url: '/device/verify', heading: 'Device Authentication' },
37+
{ name: 'device success', url: '/device/success', heading: 'Device logged in' },
38+
39+
// Settings
40+
{ name: 'settings profile', url: '/settings/profile', heading: 'Profile' },
41+
{ name: 'settings SSH keys', url: '/settings/ssh-keys', heading: 'SSH Keys' },
42+
{
43+
name: 'settings access tokens',
44+
url: '/settings/access-tokens',
45+
heading: 'Access Tokens',
46+
},
47+
48+
// Silo
49+
{ name: 'projects list', url: '/projects', heading: 'Projects' },
50+
{
51+
name: 'silo image edit',
52+
url: '/images/arch-2022-06-01/edit',
53+
heading: 'Silo image',
54+
exact: true,
55+
},
56+
{ name: 'silo utilization', url: '/utilization', heading: 'Utilization' },
57+
{ name: 'silo access', url: '/access', heading: 'Silo Access' },
58+
59+
// Project - Instances
60+
{ name: 'instances list', url: `${p}/instances`, heading: 'Instances' },
61+
{ name: 'instance create', url: `${p}/instances-new`, heading: 'Create instance' },
62+
{ name: 'instance storage tab', url: `${p}/instances/db1/storage`, heading: 'db1' },
63+
{ name: 'instance networking tab', url: `${p}/instances/db1/networking`, heading: 'db1' },
64+
{ name: 'instance metrics cpu', url: `${p}/instances/db1/metrics/cpu`, heading: 'db1' },
65+
{ name: 'instance metrics disk', url: `${p}/instances/db1/metrics/disk`, heading: 'db1' },
66+
{
67+
name: 'instance metrics network',
68+
url: `${p}/instances/db1/metrics/network`,
69+
heading: 'db1',
70+
},
71+
{ name: 'instance connect tab', url: `${p}/instances/db1/connect`, heading: 'db1' },
72+
{ name: 'instance settings tab', url: `${p}/instances/db1/settings`, heading: 'db1' },
73+
74+
// Project - Disks
75+
{ name: 'disks list', url: `${p}/disks`, heading: 'Disks' },
76+
{ name: 'create disk', url: `${p}/disks-new`, heading: 'Create disk' },
77+
78+
// Project - Snapshots, Images
79+
{ name: 'snapshots list', url: `${p}/snapshots`, heading: 'Snapshots' },
80+
{ name: 'images list', url: `${p}/images`, heading: 'Images' },
81+
{ name: 'image upload', url: `${p}/images-new`, heading: 'Upload image' },
82+
83+
// Project - VPCs
84+
{ name: 'vpcs list', url: `${p}/vpcs`, heading: 'VPCs' },
85+
{
86+
name: 'vpc firewall rules',
87+
url: `${p}/vpcs/mock-vpc/firewall-rules`,
88+
heading: 'mock-vpc',
89+
},
90+
{ name: 'vpc subnets', url: `${p}/vpcs/mock-vpc/subnets`, heading: 'mock-vpc' },
91+
{ name: 'vpc routers', url: `${p}/vpcs/mock-vpc/routers`, heading: 'mock-vpc' },
92+
{
93+
name: 'vpc internet gateways',
94+
url: `${p}/vpcs/mock-vpc/internet-gateways`,
95+
heading: 'mock-vpc',
96+
},
97+
{
98+
name: 'vpc router detail',
99+
url: `${p}/vpcs/mock-vpc/routers/mock-custom-router`,
100+
heading: 'mock-custom-router',
101+
},
102+
103+
// Project - Networking
104+
{ name: 'floating IPs', url: `${p}/floating-ips`, heading: 'Floating IPs' },
105+
{ name: 'external subnets', url: `${p}/external-subnets`, heading: 'External Subnets' },
106+
107+
// Project - Other
108+
{ name: 'project access', url: `${p}/access`, heading: 'Project Access' },
109+
{ name: 'affinity groups', url: `${p}/affinity`, heading: 'Affinity Groups' },
110+
{
111+
name: 'anti-affinity group detail',
112+
url: `${p}/affinity/romulus-remus`,
113+
heading: 'romulus-remus',
114+
},
115+
116+
// System - Silos
117+
{ name: 'system silos list', url: '/system/silos', heading: 'Silos' },
118+
{ name: 'silo detail idps', url: '/system/silos/maze-war/idps', heading: 'maze-war' },
119+
{
120+
name: 'silo detail ip pools',
121+
url: '/system/silos/maze-war/ip-pools',
122+
heading: 'maze-war',
123+
},
124+
{
125+
name: 'silo detail subnet pools',
126+
url: '/system/silos/maze-war/subnet-pools',
127+
heading: 'maze-war',
128+
},
129+
{ name: 'silo detail quotas', url: '/system/silos/maze-war/quotas', heading: 'maze-war' },
130+
{
131+
name: 'silo detail fleet roles',
132+
url: '/system/silos/maze-war/fleet-roles',
133+
heading: 'maze-war',
134+
},
135+
{ name: 'silo detail scim', url: '/system/silos/maze-war/scim', heading: 'maze-war' },
136+
137+
// System - Utilization
138+
{ name: 'system utilization', url: '/system/utilization', heading: 'Utilization' },
139+
{
140+
name: 'system utilization metrics tab',
141+
url: '/system/utilization?tab=metrics',
142+
heading: 'Utilization',
143+
},
144+
145+
// System - Networking
146+
{ name: 'system ip pools', url: '/system/networking/ip-pools', heading: 'IP Pools' },
147+
{
148+
name: 'ip pool detail',
149+
url: '/system/networking/ip-pools/ip-pool-1',
150+
heading: 'ip-pool-1',
151+
},
152+
{
153+
name: 'ip pool silos tab',
154+
url: '/system/networking/ip-pools/ip-pool-1?tab=silos',
155+
heading: 'ip-pool-1',
156+
},
157+
{
158+
name: 'system subnet pools',
159+
url: '/system/networking/subnet-pools',
160+
heading: 'Subnet Pools',
161+
},
162+
{
163+
name: 'subnet pool detail',
164+
url: '/system/networking/subnet-pools/default-v4-subnet-pool',
165+
heading: 'default-v4-subnet-pool',
166+
},
167+
{
168+
name: 'subnet pool silos tab',
169+
url: '/system/networking/subnet-pools/default-v4-subnet-pool?tab=silos',
170+
heading: 'default-v4-subnet-pool',
171+
},
172+
173+
// System - Inventory
174+
{ name: 'inventory sleds', url: '/system/inventory/sleds', heading: 'Inventory' },
175+
{ name: 'inventory disks', url: '/system/inventory/disks', heading: 'Inventory' },
176+
{
177+
name: 'sled instances',
178+
url: '/system/inventory/sleds/c2519937-44a4-493b-9b38-5c337c597d08/instances',
179+
heading: 'Sled',
180+
},
181+
182+
// System - Update & Access
183+
{ name: 'system update', url: '/system/update', heading: 'System Update' },
184+
{ name: 'fleet access', url: '/system/access', heading: 'Fleet Access' },
185+
186+
// Error
187+
{ name: 'not found', url: '/nonexistent', heading: 'Page not found' },
188+
]
48189

49-
test('disks list', async ({ page }) => {
50-
await page.goto('/projects/mock-project/disks')
51-
await expect(page.getByRole('heading', { name: 'Disks' })).toBeVisible()
52-
await expect(page).toHaveScreenshot('disks-list.png', { fullPage: true })
53-
})
190+
test.describe('Visual Regression', { tag: '@visual' }, () => {
191+
for (const { name, url, heading, exact } of pages) {
192+
const screenshot = name.replaceAll(' ', '-') + '.png'
193+
test(name, async ({ page }) => {
194+
await page.goto(url, { waitUntil: 'networkidle' })
195+
await expect(page.getByRole('heading', { name: heading, exact })).toBeVisible()
196+
await expect(page).toHaveScreenshot(screenshot, fullPage)
197+
})
198+
}
54199

55-
test('vpcs list', async ({ page }) => {
56-
await page.goto('/projects/mock-project/vpcs')
57-
await expect(page.getByRole('heading', { name: 'VPCs' })).toBeVisible()
58-
await expect(page).toHaveScreenshot('vpcs-list.png', { fullPage: true })
59-
})
200+
// Special cases that don't fit the standard pattern
60201

61-
test('snapshots list', async ({ page }) => {
62-
await page.goto('/projects/mock-project/snapshots')
63-
await expect(page.getByRole('heading', { name: 'Snapshots' })).toBeVisible()
64-
await expect(page).toHaveScreenshot('snapshots-list.png', { fullPage: true })
65-
})
66-
67-
test('images list', async ({ page }) => {
68-
await page.goto('/projects/mock-project/images')
69-
await expect(page.getByRole('heading', { name: 'Images' })).toBeVisible()
70-
await expect(page).toHaveScreenshot('images-list.png', { fullPage: true })
202+
test('login form', async ({ page }) => {
203+
await page.goto('/login/default-silo/local', { waitUntil: 'networkidle' })
204+
await expect(page).toHaveURL(/\/login/)
205+
await expect(page).toHaveScreenshot('login-form.png')
71206
})
72207

73-
test('silo images list', async ({ page }) => {
74-
await page.goto('/images')
208+
test('silo images', async ({ page }) => {
209+
await page.goto('/images', { waitUntil: 'networkidle' })
75210
await expect(page.getByRole('heading', { name: 'Silo Images' })).toBeVisible()
76-
await expect(page).toHaveScreenshot('silo-images.png', { fullPage: true })
77-
await page.click('role=button[name="Promote image"]')
78-
await expect(page).toHaveScreenshot('silo-images-promote.png', { fullPage: true })
79-
})
80-
81-
test('silo image', async ({ page }) => {
82-
await page.goto('/images/arch-2022-06-01/edit')
83-
await expect(
84-
page.getByRole('heading', { name: 'Silo image', exact: true })
85-
).toBeVisible()
86-
await expect(page).toHaveScreenshot('silo-image.png', { fullPage: true })
87-
})
88-
89-
test('system utilization', async ({ page }) => {
90-
await page.goto('/utilization')
91-
await expect(page.getByRole('heading', { name: 'Utilization' })).toBeVisible()
92-
await expect(page).toHaveScreenshot('system-utilization.png', { fullPage: true })
93-
})
94-
95-
test('system silos list', async ({ page }) => {
96-
await page.goto('/system/silos')
97-
await expect(page.getByRole('heading', { name: 'Silos' })).toBeVisible()
98-
await expect(page).toHaveScreenshot('system-silos.png', { fullPage: true })
211+
await expect(page).toHaveScreenshot('silo-images.png', fullPage)
212+
await page.getByRole('button', { name: 'Promote image' }).click()
213+
await page.waitForLoadState('networkidle')
214+
await expect(page).toHaveScreenshot('silo-images-promote.png', fullPage)
99215
})
100216

101-
test('system networking ip pools', async ({ page }) => {
102-
await page.goto('/system/networking/ip-pools')
103-
await expect(page.getByRole('heading', { name: 'IP Pools' })).toBeVisible()
104-
await expect(page).toHaveScreenshot('system-ip-pools.png', { fullPage: true })
105-
})
106-
107-
test('settings profile', async ({ page }) => {
108-
await page.goto('/settings/profile')
109-
await expect(page.getByRole('heading', { name: 'Profile' })).toBeVisible()
110-
await expect(page).toHaveScreenshot('settings-profile.png', { fullPage: true })
217+
test('saml login', async ({ page }) => {
218+
await page.goto('/login/default-silo/saml/mock-idp', { waitUntil: 'networkidle' })
219+
await expect(page).toHaveURL(/\/login/)
220+
await expect(page).toHaveScreenshot('saml-login.png')
111221
})
112222

113-
test('login form', async ({ page }) => {
114-
await page.goto('/login/default-silo/local')
115-
116-
await expect(page).toHaveURL(/\/login/)
117-
await expect(page).toHaveScreenshot('login-form.png')
223+
test('serial console', async ({ page }) => {
224+
await page.goto(`${p}/instances/db1/serial-console`, { waitUntil: 'networkidle' })
225+
await expect(page.getByText('Serial Console')).toBeVisible()
226+
await expect(page).toHaveScreenshot('serial-console.png', fullPage)
118227
})
119228

120229
test('command menu', async ({ page }) => {
121230
await page.keyboard.press(`ControlOrMeta+k`)
122-
await expect(page).toHaveScreenshot('command-menu.png', { fullPage: true })
231+
await page.waitForLoadState('networkidle')
232+
await expect(page).toHaveScreenshot('command-menu.png', fullPage)
123233
})
124234
})

0 commit comments

Comments
 (0)