Skip to content

Commit 1e7df31

Browse files
test(small): Fix Playwright route mocking order for WorkoutTableHeader VRT (#9529)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: arii <342438+arii@users.noreply.github.com>
1 parent d1a19d7 commit 1e7df31

8 files changed

Lines changed: 87 additions & 5 deletions

File tree

tests/playwright/lib/accessibility.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,19 @@ import { v4 as uuidv4 } from 'uuid'
1010
* @throws An error if any accessibility violations are found.
1111
*/
1212
export async function checkAccessibility(target: Page | Locator) {
13-
const page = 'page' in target ? target.page() : (target as Page)
13+
// Safely extract the page object based on the target type
14+
const page = 'page' in target ? (target as Locator).page() : (target as Page)
1415
const uniqueId = `axe-${uuidv4()}`
1516
let selector: string | undefined = undefined
1617

1718
// If the target is a Locator, we need to add a temporary unique attribute
1819
// to it so we can scope the accessibility scan to that element.
1920
if ('page' in target) {
20-
await target.evaluate((node, id) => node.setAttribute(id, ''), uniqueId)
21+
await (target as Locator).waitFor({ state: 'attached' })
22+
await (target as Locator).evaluate(
23+
(node, id) => node.setAttribute(id, ''),
24+
uniqueId
25+
)
2126
selector = `[${uniqueId}]`
2227
}
2328

tests/playwright/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export {
7272
getDynamicContentMasks,
7373
getHrMasks,
7474
getTimerMasks,
75+
getSpotifyMasks,
7576
} from './masks'
7677

7778
// ============================================================================

tests/playwright/lib/masks.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const VRT_MASK_SELECTORS = {
2121
timerCountdown: '[data-testid="timer-countdown"]',
2222
timerPhaseLabel: '[data-testid="timer-phase-label"]',
2323
hrTimeSeriesChart: '[data-testid="hr-time-series-chart"]',
24+
spotifyCurrentTrack: '[data-testid="spotify-current-track-name"]',
2425
} as const
2526

2627
/**
@@ -67,3 +68,7 @@ export function getTimerMasks(page: Page): Locator[] {
6768
page.locator(VRT_MASK_SELECTORS.timerPhaseLabel),
6869
]
6970
}
71+
72+
export function getSpotifyMasks(page: Page): Locator[] {
73+
return [page.locator(VRT_MASK_SELECTORS.spotifyCurrentTrack)]
74+
}

tests/playwright/lib/setup.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ export async function navigateAndWait(
127127
})
128128
.catch(() => console.warn('Test controls not found within timeout'))
129129

130+
// Trigger user interaction to unlock AudioContext (seen in logs preventing muted states)
131+
try {
132+
await page.mouse.click(0, 0)
133+
} catch (e) {
134+
console.warn(`[navigateAndWait] Failed to unlock AudioContext: ${e}`)
135+
}
136+
130137
// Stabilize VRT by disabling animations, transitions, and backdrop filters
131138
await page.addStyleTag({
132139
content: `
@@ -136,6 +143,13 @@ export async function navigateAndWait(
136143
backdrop-filter: none !important;
137144
-webkit-backdrop-filter: none !important;
138145
}
146+
body, html, * {
147+
scrollbar-width: none !important;
148+
-ms-overflow-style: none !important;
149+
}
150+
::-webkit-scrollbar {
151+
display: none !important;
152+
}
139153
[data-testid="main-content-layout"] {
140154
opacity: 1 !important;
141155
transform: none !important;
@@ -162,6 +176,35 @@ export async function resetServerState(
162176
}
163177
}
164178

179+
/**
180+
* Comprehensive setup for visual regression tests.
181+
* Creates a clean browser context, initializes all required pages,
182+
* and prepares them for snapshot testing.
183+
*
184+
* @param browser - The Playwright Browser fixture
185+
* @returns An object containing the context and all created pages.
186+
*/
187+
export async function mockSpotifyEnvironment(
188+
contextOrPage: BrowserContext | Page
189+
) {
190+
// Mock internal Auth API for Spotify VRT to prevent 401s and websocket reset loops
191+
await contextOrPage.route('**/api/spotify/access-token', async (route) => {
192+
await route.fulfill({
193+
status: 200,
194+
contentType: 'application/json',
195+
body: JSON.stringify({
196+
accessToken: 'mock_token',
197+
expiresAt: Date.now() + 3600000,
198+
}),
199+
})
200+
})
201+
202+
// Block the real Spotify SDK from loading and erroring out
203+
await contextOrPage.route('https://sdk.scdn.co/spotify-player.js', (route) =>
204+
route.abort()
205+
)
206+
}
207+
165208
/**
166209
* Comprehensive setup for visual regression tests.
167210
* Creates a clean browser context, initializes all required pages,
@@ -195,6 +238,8 @@ export async function setupVisualRegressionTest(browser: Browser): Promise<{
195238
})
196239
})
197240

241+
await mockSpotifyEnvironment(context)
242+
198243
// Create all pages in parallel for efficiency
199244
const [dashboardPage, controlPage, mockPage] = await Promise.all([
200245
context.newPage(),
@@ -250,6 +295,8 @@ export async function setupMinimalVisualRegressionTest(
250295
})
251296
})
252297

298+
await mockSpotifyEnvironment(page)
299+
253300
// Mock the iframe for the root path before navigation
254301
if (path === '' || path === '/') {
255302
await mockGoogleDocIframe(page)

tests/playwright/lib/visual.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,15 @@ export async function takeScreenshot(
4747
) {
4848
const { skipA11y = false, ...screenshotOptions } = options
4949

50+
// Force layout recalculation for tablet viewports without invalid casting
51+
await target.evaluate(() => window.scrollTo(0, 0))
52+
5053
if (!skipA11y) {
5154
await checkAccessibility(target)
5255
}
5356

5457
await expect(target).toHaveScreenshot(snapshotName, {
58+
scale: 'css', // Prevent high-DPI (Retina) scaling mismatches in CI
5559
...SCREENSHOT_OPTIONS,
5660
...screenshotOptions,
5761
})
@@ -146,7 +150,7 @@ export async function takeDashboardScreenshot(
146150
...getHrMasks(page),
147151
page.getByTestId('calorie-count'),
148152
page.getByTestId('google-doc-viewer-iframe'),
149-
page.getByTestId('workout-table-viewer'),
153+
page.getByTestId('workout-table-header'),
150154
page.locator('.MUI-Charts-root'),
151155
],
152156
maxDiffPixelRatio: 0.08, // Higher tolerance for font rendering in CI

tests/playwright/vrt-components.spec.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ import {
44
setupMinimalVisualRegressionTest,
55
mockSpotifyPlaybackState,
66
mockLoggedInSession,
7+
resetServerState,
8+
getSpotifyMasks,
79
} from './lib'
810
import { checkAccessibility } from './lib/accessibility'
911
import { takeScreenshot } from './lib/visual'
1012
import { waitForPageReady } from './lib/waits'
1113
import { VRT_TIMEOUTS } from './lib/timeouts'
1214

1315
test.describe('Component-Specific VRT', () => {
14-
test.beforeEach(async ({ dashboardPage }) => {
16+
test.beforeEach(async ({ dashboardPage, request }) => {
17+
await resetServerState(request)
1518
await setupMinimalVisualRegressionTest(dashboardPage, '/')
1619
})
1720

@@ -68,6 +71,15 @@ test.describe('Component-Specific VRT', () => {
6871
})
6972

7073
test('WorkoutTableHeader rendering', async ({ dashboardPage }) => {
74+
// Setup network interception first
75+
await dashboardPage.route('/api/workout*', async (route) => {
76+
await route.fulfill({
77+
json: {
78+
headers: ['Exercise', 'Sets', 'Reps'],
79+
},
80+
})
81+
})
82+
7183
// Ensure we are in native mode for this test
7284
await dashboardPage.goto('/?native=true')
7385
await waitForPageReady(dashboardPage)
@@ -128,15 +140,23 @@ test.describe('Component-Specific VRT', () => {
128140
})
129141
await selectorButton.click()
130142

131-
const menu = dashboardPage.getByTestId('spotify-device-selector-menu-paper')
143+
const menu = dashboardPage
144+
.locator('[data-testid="spotify-device-selector-menu-paper"]')
145+
.last()
146+
147+
// Give the menu time to mount in the portal and stabilize before checking visibility
132148
await expect(menu).toBeVisible()
133149

150+
// Wait for the opacity transition to finish rendering
151+
await expect(menu).toHaveCSS('opacity', '1')
152+
134153
// Perform manual accessibility check on the specific menu element to ensure context validity
135154
await checkAccessibility(menu)
136155

137156
await takeScreenshot(menu, 'spotify-device-selector-menu.png', {
138157
threshold: 0.2, // Tighter threshold for the Paper element
139158
skipA11y: true, // Accessibility checked manually above
159+
mask: getSpotifyMasks(dashboardPage),
140160
})
141161
})
142162

-38.2 KB
Loading
-74.2 KB
Loading

0 commit comments

Comments
 (0)