Skip to content

Commit e5a313b

Browse files
committed
Update 20 e2e files to use BasePage POM that were using Playwright page object directly
1 parent b49215a commit e5a313b

25 files changed

Lines changed: 550 additions & 226 deletions

playwright.config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,7 @@ export default defineConfig({
119119
},
120120
}),
121121

122-
// path to the global setup files.
123-
//globalSetup: require.resolve('./global-setup'),
122+
globalSetup: './test/e2e/global-setup',
124123

125124
// path to the global teardown files.
126125
//globalTeardown: require.resolve('./global-teardown'),

src/lib/@types/PromiseRejectionEvent.ts renamed to src/components/scripts/@types/PromiseRejectionEvent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* The PromiseRejectionEvent constructor is a Web API and not provided by JSDOM
2+
* The PromiseRejectionEvent constructor is a Web API and not provided by Happy Dom
33
*
44
* @see [PromiseRejectionEvent is not defined](https://github.com/jsdom/jsdom/issues/2401)
55
*/

src/lib/config/environmentalVariableValidation.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@ import type { AstroUserConfig } from 'astro'
77
* import { SERVER_API_URL} from "astro:env/server";
88
* <script>import { API_URL } from "astro:env/client";</script>
99
*
10-
* Public client variables end up in both the final client and server bundles, and can
11-
* be accessed from both client and server through the astro:env/client module. Public
12-
* server variables end up in the final server bundle. Secret server variables are not
13-
* part of the final server bundle and are only validated at runtime.
10+
* Variables are accessed through import { MY_VAR } from 'astro:env/client'
11+
* or import { MY_VAR } from 'astro:env/server', ensuring only the intended
12+
* variables are available in each context.
13+
*
14+
* Public client variables: Available in both client and server bundles.
15+
* Public server variables: Only available in the server bundle.
16+
* Secret server variables: Only available in the server bundle and not included in the final bundle.
1417
*/
1518

1619
export const environmentalVariablesConfig: AstroUserConfig['env'] = {
1720
schema: {
1821
DEV_SERVER_PORT: envField.number({
19-
context: 'server',
22+
context: 'client',
2023
access: 'public',
2124
optional: true,
2225
default: 4321,
@@ -40,35 +43,35 @@ export const environmentalVariablesConfig: AstroUserConfig['env'] = {
4043
CRON_SECRET: envField.string({
4144
context: 'server',
4245
access: 'public',
43-
optional: true,
46+
optional: false,
4447
}),
4548
/**
4649
* Site uses Vercel Upstash integration for rate limiting on API endpoints
4750
*/
4851
KV_URL: envField.string({
4952
context: 'server',
5053
access: 'public',
51-
optional: true,
54+
optional: false,
5255
}),
5356
KV_REST_API_URL: envField.string({
5457
context: 'server',
5558
access: 'public',
56-
optional: true,
59+
optional: false,
5760
}),
5861
KV_REST_API_TOKEN: envField.string({
5962
context: 'server',
60-
access: 'public',
61-
optional: true,
63+
access: 'secret',
64+
optional: false,
6265
}),
6366
KV_REST_API_READ_ONLY_TOKEN: envField.string({
6467
context: 'server',
6568
access: 'public',
66-
optional: true,
69+
optional: false,
6770
}),
6871
REDIS_URL: envField.string({
6972
context: 'server',
7073
access: 'public',
71-
optional: true,
74+
optional: false,
7275
}),
7376
/**
7477
* Site uses Resend for sending site emails
@@ -84,38 +87,38 @@ export const environmentalVariablesConfig: AstroUserConfig['env'] = {
8487
SENTRY_AUTH_TOKEN: envField.string({
8588
context: 'server',
8689
access: 'public',
87-
optional: true, // Only required for uploading source maps during build
90+
optional: false, // Only required for uploading source maps during build
8891
}),
8992
PUBLIC_SENTRY_DSN: envField.string({
9093
context: 'client',
9194
access: 'public',
92-
optional: true, // Optional - Sentry only enabled if provided
95+
optional: false, // Optional - Sentry only enabled if provided
9396
}),
9497
/**
9598
* Site uses Suprabase for managing GDPR consent records
9699
*/
97100
PUBLIC_SUPABASE_URL: envField.string({
98101
context: 'client',
99102
access: 'public',
100-
optional: true,
103+
optional: false,
101104
}),
102105
PUBLIC_SUPABASE_KEY: envField.string({
103106
context: 'server',
104107
access: 'public',
105-
optional: true,
108+
optional: false,
106109
}),
107110
SUPABASE_SERVICE_ROLE_KEY: envField.string({
108111
context: 'server',
109112
access: 'secret',
110-
optional: true,
113+
optional: false,
111114
}),
112115
/**
113116
* Site uses ConvertKit for managing newsletter subscriptions
114117
*/
115118
WEBMENTION_IO_TOKEN: envField.string({
116119
context: 'server',
117120
access: 'secret',
118-
optional: true, // Optional - WebMentions only fetched if provided
121+
optional: false,
119122
}),
120123
},
121124
}

test/e2e/global-setup.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { FullConfig } from '@playwright/test'
2+
3+
async function globalSetup(_config: FullConfig) {
4+
process.env['PLAYWRIGHT'] = 'true'
5+
}
6+
7+
export default globalSetup

test/e2e/helpers/pageObjectModels/BasePage.ts

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,16 @@ export class BasePage {
5555
}
5656

5757
return response
58-
} /**
58+
}
59+
60+
/**
61+
* Reload the current page
62+
*/
63+
async reload(options?: { timeout?: number; waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' }): Promise<null | Response> {
64+
return await this._page.reload(options)
65+
}
66+
67+
/**
5968
* Dismiss cookie consent modal if it's visible
6069
*/
6170
private async dismissCookieModal(): Promise<void> {
@@ -133,6 +142,13 @@ export class BasePage {
133142
await this._page.setViewportSize({ width, height })
134143
}
135144

145+
/**
146+
* Set viewport size using object parameter
147+
*/
148+
async setViewportSize(size: { width: number; height: number }): Promise<void> {
149+
await this._page.setViewportSize(size)
150+
}
151+
136152
/**
137153
* ================================================================
138154
*
@@ -214,6 +230,34 @@ export class BasePage {
214230
await this._page.keyboard.press(key)
215231
}
216232

233+
/**
234+
* Type text using keyboard
235+
*/
236+
async type(text: string): Promise<void> {
237+
await this._page.keyboard.type(text)
238+
}
239+
240+
/**
241+
* Get keyboard object for advanced keyboard operations
242+
*/
243+
get keyboard() {
244+
return this._page.keyboard
245+
}
246+
247+
/**
248+
* Get mouse object for advanced mouse operations
249+
*/
250+
get mouse() {
251+
return this._page.mouse
252+
}
253+
254+
/**
255+
* Get touchscreen object for touch operations
256+
*/
257+
get touchscreen() {
258+
return this._page.touchscreen
259+
}
260+
217261
/**
218262
* Hover over element
219263
*/
@@ -236,6 +280,36 @@ export class BasePage {
236280
await this._page.waitForTimeout(timeout)
237281
}
238282

283+
/**
284+
* Wait for specified timeout in milliseconds
285+
* Alias for wait() method
286+
* @deprecated Use event-based waits like waitForPageLoad() instead
287+
*/
288+
async waitForTimeout(timeout: number): Promise<void> {
289+
await this._page.waitForTimeout(timeout)
290+
}
291+
292+
/**
293+
* Wait for a custom function to return truthy value
294+
*/
295+
async waitForFunction<R>(
296+
pageFunction: () => R | Promise<R>,
297+
arg?: unknown,
298+
options?: { timeout?: number }
299+
): Promise<void> {
300+
await this._page.waitForFunction(pageFunction, arg, options)
301+
}
302+
303+
/**
304+
* Intercept and modify network requests
305+
*/
306+
async route(
307+
url: string | RegExp | ((url: URL) => boolean),
308+
handler: (route: import('@playwright/test').Route) => void
309+
): Promise<void> {
310+
await this._page.route(url, handler)
311+
}
312+
239313
/**
240314
* Wait for Astro page load event
241315
* Use this instead of waitForTimeout when testing View Transitions
@@ -287,6 +361,24 @@ export class BasePage {
287361
await this._page.waitForURL(urlPattern, options)
288362
}
289363

364+
/**
365+
* Wait for a response matching the URL pattern
366+
*
367+
* @param urlPattern - URL or pattern to match
368+
* @param options - Timeout and other options
369+
*
370+
* @example
371+
* ```ts
372+
* const response = await page.waitForResponse('/api/newsletter')
373+
* ```
374+
*/
375+
async waitForResponse(
376+
urlPattern: string | RegExp | ((response: Response) => boolean),
377+
options?: { timeout?: number }
378+
): Promise<Response> {
379+
return await this._page.waitForResponse(urlPattern, options)
380+
}
381+
290382
/**
291383
* ================================================================
292384
*
@@ -302,6 +394,38 @@ export class BasePage {
302394
return this._page.url()
303395
}
304396

397+
/**
398+
* Get page URL (alias for getCurrentUrl)
399+
*/
400+
url(): string {
401+
return this._page.url()
402+
}
403+
404+
/**
405+
* Get page title
406+
*/
407+
async title(): Promise<string> {
408+
return this._page.title()
409+
}
410+
411+
/**
412+
* Get the browser context that the page belongs to
413+
*/
414+
context() {
415+
return this._page.context()
416+
}
417+
418+
/**
419+
* Emulate media features (e.g., prefers-color-scheme, reduced-motion)
420+
*/
421+
async emulateMedia(options: {
422+
colorScheme?: 'light' | 'dark' | 'no-preference'
423+
reducedMotion?: 'reduce' | 'no-preference'
424+
forcedColors?: 'active' | 'none'
425+
}): Promise<void> {
426+
await this._page.emulateMedia(options)
427+
}
428+
305429
/**
306430
* Verify page URL matches expected pattern
307431
*/

0 commit comments

Comments
 (0)