Skip to content

Commit c488650

Browse files
authored
Add FOUC integration tests for generated CSS (#4005)
Adds Playwright integration coverage that proves the detector catches a simulated FOUC and verifies generated CSS is available before hydration across browser engines. Merged after accelerated-RC gate: current-head checks green, full CI/manual workflows green, current-head claude-review passed, zero unresolved review threads, and PR description contains the merge confidence rationale.
1 parent 93fa46b commit c488650

2 files changed

Lines changed: 109 additions & 0 deletions

File tree

react_on_rails/spec/dummy/app/views/layouts/application.html.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
<%= yield :head %>
1616

17+
<%= stylesheet_pack_tag %>
1718
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', defer: true) %>
1819

1920
<%= csrf_meta_tags %>
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { test, expect } from '@playwright/test';
2+
import { app } from '../../support/on-rails';
3+
4+
const cssModulesExamplePath = '/css_modules_images_fonts_example';
5+
const cssModulesHeadingText = 'This should be open sans light green.';
6+
const expectedStyledColor = 'rgb(0, 128, 0)';
7+
8+
async function delayCompiledJavaScript(page) {
9+
const delayedRequests = [];
10+
let releaseScripts;
11+
const releaseGate = new Promise((resolve) => {
12+
releaseScripts = resolve;
13+
});
14+
15+
await page.route(/\/webpack\/test\/.*\.js(\?.*)?$/, async (route) => {
16+
delayedRequests.push(route.request().url());
17+
await releaseGate;
18+
await route.continue();
19+
});
20+
21+
return {
22+
delayedRequests,
23+
releaseScripts,
24+
};
25+
}
26+
27+
async function readCssModulesHeadingStyle(page) {
28+
const heading = page.getByRole('heading', { name: cssModulesHeadingText });
29+
await expect(heading).toBeVisible({ timeout: 0 });
30+
31+
return heading.evaluate((element, styledColor) => {
32+
const style = window.getComputedStyle(element);
33+
34+
return {
35+
className: element.className,
36+
color: style.color,
37+
fontFamily: style.fontFamily,
38+
hasFouc: style.color !== styledColor,
39+
};
40+
}, expectedStyledColor);
41+
}
42+
43+
test.describe('FOUC regression coverage', () => {
44+
test.beforeEach(async () => {
45+
await app('clean');
46+
});
47+
48+
test('detector flags a deliberately unstyled first paint', async ({ page }) => {
49+
await page.route(/\/webpack\/test\/.*\.css(\?.*)?$/, (route) =>
50+
route.fulfill({
51+
status: 200,
52+
contentType: 'text/css',
53+
body: '',
54+
}),
55+
);
56+
57+
const { releaseScripts } = await delayCompiledJavaScript(page);
58+
59+
try {
60+
await page.goto(cssModulesExamplePath, { waitUntil: 'commit' });
61+
62+
await expect
63+
.poll(async () => readCssModulesHeadingStyle(page))
64+
.toMatchObject({
65+
hasFouc: true,
66+
});
67+
} finally {
68+
releaseScripts();
69+
}
70+
});
71+
72+
test('server-rendered CSS module content is styled before generated React packs hydrate', async ({
73+
page,
74+
}) => {
75+
const { delayedRequests, releaseScripts } = await delayCompiledJavaScript(page);
76+
const stylesheetResponse = page.waitForResponse(
77+
(response) =>
78+
response.url().includes('/webpack/test/css/generated/CssModulesImagesFontsExample') &&
79+
response.url().endsWith('.css') &&
80+
response.status() === 200,
81+
{ timeout: 15000 },
82+
);
83+
84+
try {
85+
await page.goto(cssModulesExamplePath, { waitUntil: 'commit' });
86+
await stylesheetResponse;
87+
88+
const beforeHydration = await readCssModulesHeadingStyle(page);
89+
90+
expect(delayedRequests).not.toHaveLength(0);
91+
expect(beforeHydration).toMatchObject({
92+
color: expectedStyledColor,
93+
hasFouc: false,
94+
});
95+
} finally {
96+
releaseScripts();
97+
}
98+
99+
await page.waitForLoadState('networkidle');
100+
101+
await expect
102+
.poll(async () => readCssModulesHeadingStyle(page))
103+
.toMatchObject({
104+
color: expectedStyledColor,
105+
hasFouc: false,
106+
});
107+
});
108+
});

0 commit comments

Comments
 (0)