fix(pwa): avoid CSP-blocked stylesheet deferral

This commit is contained in:
4gray committed 2026-05-24 14:59:08 +03:00
1 parent 697eab6e73
commit bb4e386c6c
6 files changed
+113 -5

No files matched your search

+15 -4
View File
@@ -2,8 +2,19 @@ import { defineConfig, devices } from '@playwright/test';
import { nxE2EPreset } from '@nx/playwright/preset';
import { workspaceRoot } from '@nx/devkit';
const isStaticPwaE2E = process.env['IPTVNATOR_E2E_STATIC_PWA'] === '1';
const staticPwaPort = process.env['IPTVNATOR_E2E_STATIC_PORT'] ?? '4300';
// For CI, you may want to set BASE_URL to the deployed application.
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
const baseURL =
process.env['BASE_URL'] ||
(isStaticPwaE2E
? `http://localhost:${staticPwaPort}`
: 'http://localhost:4200');
const webServerCommand =
isStaticPwaE2E
? `pnpm nx run web:serve-static --port=${staticPwaPort}`
: 'pnpm nx run web:serve';
const reuseExistingWebServer = isStaticPwaE2E ? false : !process.env['CI'];
/**
* Read environment variables from file.
@@ -29,9 +40,9 @@ export default defineConfig({
*/
webServer: [
{
command: 'pnpm nx run web:serve',
url: 'http://localhost:4200',
reuseExistingServer: !process.env['CI'],
command: webServerCommand,
url: baseURL,
reuseExistingServer: reuseExistingWebServer,
cwd: workspaceRoot,
},
{
+69
View File
@@ -0,0 +1,69 @@
import { expect, test } from '@playwright/test';
test('@pwa-static PWA build applies the full stylesheet under CSP', async ({
page,
}) => {
const cspConsoleErrors: string[] = [];
page.on('console', (message) => {
if (
message.type() === 'error' &&
/content security policy|inline event handler|onload/i.test(
message.text()
)
) {
cspConsoleErrors.push(message.text());
}
});
await page.goto('/');
const stylesheetLinks = page.locator('link[rel="stylesheet"]');
await expect(stylesheetLinks.first()).toBeAttached();
const stylesheetMedia = await stylesheetLinks.evaluateAll((links) =>
links.map((link) => ({
href: link.getAttribute('href'),
media: link.getAttribute('media'),
}))
);
const appStylesheet = stylesheetMedia.find((link) =>
/^styles-.*\.css$/.test(link.href ?? '')
);
expect(appStylesheet).toBeDefined();
expect(appStylesheet?.media).not.toBe('print');
const materialIcon = page.locator('mat-icon.material-icons').first();
await expect(materialIcon).toBeVisible();
await expect
.poll(() =>
materialIcon.evaluate((icon) =>
getComputedStyle(icon).fontFamily.toLowerCase()
)
)
.toContain('material icons');
await expect
.poll(() =>
page.evaluate(() =>
document.fonts.check('24px "Material Icons"')
)
)
.toBe(true);
const ngswResponse = await page.request.get('/ngsw.json');
await expect(ngswResponse).toBeOK();
const ngswManifest = (await ngswResponse.json()) as {
assetGroups?: Array<{ urls?: string[] }>;
};
const cachedUrls = ngswManifest.assetGroups?.flatMap(
(assetGroup) => assetGroup.urls ?? []
);
expect(cachedUrls).toEqual(
expect.arrayContaining([expect.stringMatching(/^(?:\.\/|\/)media\//)])
);
expect(cspConsoleErrors).toEqual([]);
});