Skip to content

Commit 6444b7f

Browse files
committed
Add E2E test cases for environment API and client files to make sure vars are exposed in client and SSR bundles
1 parent eabf89a commit 6444b7f

9 files changed

Lines changed: 258 additions & 64 deletions

File tree

@types/window.d.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@ interface MetaColors {
1212
[key: string]: string
1313
}
1414

15+
interface EnvironmentClientSnapshot {
16+
isUnitTest: boolean
17+
isTest: boolean
18+
isE2eTest: boolean
19+
isDev: boolean
20+
isProd: boolean
21+
packageRelease: string
22+
privacyPolicyVersion: string
23+
}
24+
25+
interface EnvironmentApiSnapshot {
26+
isUnitTest: boolean
27+
isTest: boolean
28+
isE2eTest: boolean
29+
isDev: boolean
30+
isProd: boolean
31+
packageRelease: string
32+
privacyPolicyVersion: string
33+
}
34+
1535
declare global {
1636
interface Window {
1737
/**
@@ -25,6 +45,16 @@ declare global {
2545
* Test flag to indicate Playwright control
2646
*/
2747
isPlaywrightControlled?: boolean
48+
49+
/**
50+
* Snapshot of environment-client results for Playwright assertions
51+
*/
52+
environmentClientSnapshot?: EnvironmentClientSnapshot
53+
54+
/**
55+
* Snapshot of environment-api (server helper) results for Playwright assertions
56+
*/
57+
environmentApiSnapshot?: EnvironmentApiSnapshot
2858
}
2959
}
3060

astro.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const standardIntegrations = [
6060
})] : []),
6161
sitemap({
6262
serialize: createSerializeFunction({
63-
exclude: ['downloads', 'social-shares', '/articles/demo'],
63+
exclude: ['downloads', 'social-shares', '/articles/demo', 'testing'],
6464
}),
6565
}),
6666
pagesJsonWriter(),

eslint.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ export default [
255255
/** Environment file in src/pages/api is an except to the restricted paths rule */
256256
{
257257
files: [
258-
'src/pages/api/_environment/index.ts',
258+
'src/pages/api/_environment/environmentApi.ts',
259259
'src/pages/api/_logger/index.ts',
260260
],
261261
rules: {

src/components/scripts/bootstrap/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,23 @@ import {
1010
initConsentSideEffects,
1111
} from '@components/scripts/store'
1212
import { SentryBootstrap } from '@components/scripts/sentry/client'
13-
import { isProd } from '@components/scripts/utils/environmentClient'
13+
import { isProd, isDev, isE2eTest, isTest, isUnitTest } from '@components/scripts/utils/environmentClient'
1414

1515
export class AppBootstrap {
1616
static init(): void {
1717
addScriptBreadcrumb({ scriptName: 'AppBootstrap', operation: 'init' })
1818

1919
try {
20+
if (typeof window !== 'undefined' && window.isPlaywrightControlled) {
21+
window.environmentClientSnapshot = {
22+
isUnitTest: isUnitTest(),
23+
isTest: isTest(),
24+
isE2eTest: isE2eTest(),
25+
isDev: isDev(),
26+
isProd: isProd(),
27+
}
28+
}
29+
2030
/* Be careful adding script here. It runs before any script tags in components. */
2131
if (isProd()) {
2232
SentryBootstrap.init()
File renamed without changes.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
import {
3+
getPackageRelease,
4+
getPrivacyPolicyVersion,
5+
isDev,
6+
isE2eTest,
7+
isProd,
8+
isTest,
9+
isUnitTest,
10+
} from '@pages/api/_environment/environmentApi'
11+
12+
/**
13+
* This testing page intentionally renders as part of the client bundle so
14+
* Playwright can verify the statically generated output.
15+
*/
16+
export const prerender = true
17+
18+
const snapshot = {
19+
isUnitTest: isUnitTest(),
20+
isTest: isTest(),
21+
isE2eTest: isE2eTest(),
22+
isDev: isDev(),
23+
isProd: isProd(),
24+
packageRelease: getPackageRelease(),
25+
privacyPolicyVersion: getPrivacyPolicyVersion(),
26+
}
27+
28+
const formattedSnapshot = JSON.stringify(snapshot, null, 2)
29+
const scriptContent = `window.environmentApiSnapshot = ${JSON.stringify(snapshot).replace(/</g, '\\u003c')}`
30+
---
31+
32+
<!DOCTYPE html>
33+
<html lang="en">
34+
<head>
35+
<meta charset="utf-8">
36+
<title>Environment API Diagnostics</title>
37+
<meta name="robots" content="noindex, nofollow">
38+
</head>
39+
<body>
40+
<main>
41+
<h1>Environment API Diagnostics</h1>
42+
<p data-testid="environment-api-notice">
43+
This page exposes server environment helpers for automated testing only.
44+
</p>
45+
<pre id="environment-api-json">{formattedSnapshot}</pre>
46+
</main>
47+
<script type="module" set:html={scriptContent}></script>
48+
</body>
49+
</html>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
3+
---
4+
<!DOCTYPE html>
5+
<html lang="en">
6+
<head>
7+
<meta charset="utf-8">
8+
<title>Environment Client Diagnostics</title>
9+
<meta name="robots" content="noindex, nofollow">
10+
</head>
11+
<body>
12+
<main>
13+
<h1>Environment Client Diagnostics</h1>
14+
<p data-testid="environment-client-notice">
15+
This page exposes environment utilities for automated testing only.
16+
</p>
17+
<pre id="environment-client-json">Collecting client environment data…</pre>
18+
</main>
19+
<script>
20+
import {
21+
getPackageRelease,
22+
getPrivacyPolicyVersion,
23+
isDev,
24+
isE2eTest,
25+
isProd,
26+
isTest,
27+
isUnitTest,
28+
} from '@components/scripts/utils/environmentClient'
29+
30+
const snapshot = {
31+
isUnitTest: isUnitTest(),
32+
isTest: isTest(),
33+
isE2eTest: isE2eTest(),
34+
isDev: isDev(),
35+
isProd: isProd(),
36+
packageRelease: getPackageRelease(),
37+
privacyPolicyVersion: getPrivacyPolicyVersion(),
38+
}
39+
40+
window.environmentClientSnapshot = snapshot
41+
42+
const output = document.getElementById('environment-client-json')
43+
if (output) {
44+
output.textContent = JSON.stringify(snapshot, null, 2)
45+
}
46+
</script>
47+
</body>
48+
</html>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { test, expect } from '@test/e2e/helpers'
2+
import { BasePage } from '@test/e2e/helpers/pageObjectModels/BasePage'
3+
4+
type EnvironmentApiSnapshot = {
5+
isUnitTest: boolean
6+
isTest: boolean
7+
isE2eTest: boolean
8+
isDev: boolean
9+
isProd: boolean
10+
packageRelease: string
11+
privacyPolicyVersion: string
12+
}
13+
14+
const navigateToDiagnosticsPage = async (page: BasePage) => {
15+
await page.goto('/testing/environment-api', { skipCookieDismiss: true })
16+
await page.waitForLoadState('networkidle')
17+
await page.waitForFunction(() => Boolean(window.environmentApiSnapshot))
18+
}
19+
20+
const getEnvironmentSnapshot = async (page: BasePage): Promise<EnvironmentApiSnapshot> => {
21+
await navigateToDiagnosticsPage(page)
22+
return await page.evaluate(() => {
23+
if (!window.environmentApiSnapshot) {
24+
throw new Error('Environment API snapshot not initialized')
25+
}
26+
return window.environmentApiSnapshot
27+
})
28+
}
29+
30+
test.describe('Server Environment Diagnostics', () => {
31+
test('isUnitTest should return false for server snapshot generated in dev', async ({ page: playwrightPage }) => {
32+
const page = await BasePage.init(playwrightPage)
33+
const snapshot = await getEnvironmentSnapshot(page)
34+
expect(snapshot.isUnitTest).toBe(false)
35+
})
36+
37+
test('isTest should reflect server-side detection state', async ({ page: playwrightPage }) => {
38+
const page = await BasePage.init(playwrightPage)
39+
const snapshot = await getEnvironmentSnapshot(page)
40+
expect(snapshot.isTest).toBe(false)
41+
})
42+
43+
test('isE2eTest should be false on server snapshot during dev-server rendering', async ({ page: playwrightPage }) => {
44+
const page = await BasePage.init(playwrightPage)
45+
const snapshot = await getEnvironmentSnapshot(page)
46+
expect(snapshot.isE2eTest).toBe(false)
47+
})
48+
49+
test('isDev should be true for dev-server rendering', async ({ page: playwrightPage }) => {
50+
const page = await BasePage.init(playwrightPage)
51+
const snapshot = await getEnvironmentSnapshot(page)
52+
expect(snapshot.isDev).toBe(true)
53+
})
54+
55+
test('isProd should be false for dev-server rendering', async ({ page: playwrightPage }) => {
56+
const page = await BasePage.init(playwrightPage)
57+
const snapshot = await getEnvironmentSnapshot(page)
58+
expect(snapshot.isProd).toBe(false)
59+
})
60+
61+
test('package release value should be exposed from astro:env/server', async ({ page: playwrightPage }) => {
62+
const page = await BasePage.init(playwrightPage)
63+
const snapshot = await getEnvironmentSnapshot(page)
64+
expect(snapshot.packageRelease.length).toBeGreaterThan(0)
65+
})
66+
67+
test('privacy policy version value should be exposed from astro:env/server', async ({ page: playwrightPage }) => {
68+
const page = await BasePage.init(playwrightPage)
69+
const snapshot = await getEnvironmentSnapshot(page)
70+
expect(snapshot.privacyPolicyVersion.length).toBeGreaterThan(0)
71+
})
72+
})

test/e2e/specs/14-system/environmentClient.spec.ts

Lines changed: 46 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -10,87 +10,72 @@
1010
import { test, expect } from '@test/e2e/helpers'
1111
import { BasePage } from '@test/e2e/helpers/pageObjectModels/BasePage'
1212

13+
type EnvironmentSnapshot = {
14+
isUnitTest: boolean
15+
isTest: boolean
16+
isE2eTest: boolean
17+
isDev: boolean
18+
isProd: boolean
19+
packageRelease: string
20+
privacyPolicyVersion: string
21+
}
22+
23+
const navigateToDiagnosticsPage = async (page: BasePage) => {
24+
await page.goto('/testing/environment-client', { skipCookieDismiss: true })
25+
await page.waitForLoadState('networkidle')
26+
await page.waitForFunction(() => Boolean(window.environmentClientSnapshot))
27+
}
28+
29+
const getEnvironmentSnapshot = async (page: BasePage): Promise<EnvironmentSnapshot> => {
30+
await navigateToDiagnosticsPage(page)
31+
return await page.evaluate(() => {
32+
if (!window.environmentClientSnapshot) {
33+
throw new Error('Environment snapshot not initialized')
34+
}
35+
return window.environmentClientSnapshot
36+
})
37+
}
38+
1339
test.describe('Client Environment Detection Regression', () => {
1440
test('isUnitTest should return false in browser context', async ({ page: playwrightPage }) => {
1541
const page = await BasePage.init(playwrightPage)
16-
17-
// Navigate to a page so the dev server context is available
18-
await page.goto('/')
19-
await page.waitForLoadState('networkidle')
20-
21-
// Execute the environment detection function in the browser context
22-
// Import and call the function directly in the browser
23-
const isUnitTestResult = await page.evaluate(async () => {
24-
// @ts-expect-error - Browser-side import, path resolved by dev server at runtime
25-
const { isUnitTest } = await import('/src/components/scripts/utils/environmentClient.ts')
26-
return isUnitTest()
27-
})
28-
29-
// Assert that isUnitTest returns false when called in browser
30-
expect(isUnitTestResult).toBe(false)
42+
const envSnapshot = await getEnvironmentSnapshot(page)
43+
expect(envSnapshot.isUnitTest).toBe(false)
3144
})
3245

3346
test('isTest should return true in browser context', async ({ page: playwrightPage }) => {
3447
const page = await BasePage.init(playwrightPage)
35-
36-
await page.goto('/')
37-
await page.waitForLoadState('networkidle')
38-
39-
const isTestResult = await page.evaluate(async () => {
40-
// @ts-expect-error - Browser-side import, path resolved by dev server at runtime
41-
const { isTest } = await import('/src/components/scripts/utils/environmentClient.ts')
42-
return isTest()
43-
})
44-
45-
// Assert that isTest returns true in E2E test context
46-
expect(isTestResult).toBe(true)
48+
const envSnapshot = await getEnvironmentSnapshot(page)
49+
expect(envSnapshot.isTest).toBe(true)
4750
})
4851

4952
test('isE2eTest should return true in browser context', async ({ page: playwrightPage }) => {
5053
const page = await BasePage.init(playwrightPage)
51-
52-
await page.goto('/')
53-
await page.waitForLoadState('networkidle')
54-
55-
const isE2eTestResult = await page.evaluate(async () => {
56-
// @ts-expect-error - Browser-side import, path resolved by dev server at runtime
57-
const { isE2eTest } = await import('/src/components/scripts/utils/environmentClient.ts')
58-
return isE2eTest()
59-
})
60-
61-
// Assert that isE2eTest returns true when running in Playwright
62-
expect(isE2eTestResult).toBe(true)
54+
const envSnapshot = await getEnvironmentSnapshot(page)
55+
expect(envSnapshot.isE2eTest).toBe(true)
6356
})
6457

6558
test('isDev should return true in browser context', async ({ page: playwrightPage }) => {
6659
const page = await BasePage.init(playwrightPage)
67-
68-
await page.goto('/')
69-
await page.waitForLoadState('networkidle')
70-
71-
const isDevResult = await page.evaluate(async () => {
72-
// @ts-expect-error - Browser-side import, path resolved by dev server at runtime
73-
const { isDev } = await import('/src/components/scripts/utils/environmentClient.ts')
74-
return isDev()
75-
})
76-
77-
// Assert that isDev returns true when running against dev server
78-
expect(isDevResult).toBe(true)
60+
const envSnapshot = await getEnvironmentSnapshot(page)
61+
expect(envSnapshot.isDev).toBe(true)
7962
})
8063

8164
test('isProd should return false in browser context', async ({ page: playwrightPage }) => {
8265
const page = await BasePage.init(playwrightPage)
66+
const envSnapshot = await getEnvironmentSnapshot(page)
67+
expect(envSnapshot.isProd).toBe(false)
68+
})
8369

84-
await page.goto('/')
85-
await page.waitForLoadState('networkidle')
86-
87-
const isProdResult = await page.evaluate(async () => {
88-
// @ts-expect-error - Browser-side import, path resolved by dev server at runtime
89-
const { isProd } = await import('/src/components/scripts/utils/environmentClient.ts')
90-
return isProd()
91-
})
70+
test('package release is exposed from astro:env/client', async ({ page: playwrightPage }) => {
71+
const page = await BasePage.init(playwrightPage)
72+
const envSnapshot = await getEnvironmentSnapshot(page)
73+
expect(envSnapshot.packageRelease.length).toBeGreaterThan(0)
74+
})
9275

93-
// Assert that isProd returns false when running against dev server
94-
expect(isProdResult).toBe(false)
76+
test('privacy policy version is exposed from astro:env/client', async ({ page: playwrightPage }) => {
77+
const page = await BasePage.init(playwrightPage)
78+
const envSnapshot = await getEnvironmentSnapshot(page)
79+
expect(envSnapshot.privacyPolicyVersion.length).toBeGreaterThan(0)
9580
})
9681
})

0 commit comments

Comments
 (0)