11import { defineConfig , devices } from '@playwright/test'
2- import { dirname , resolve } from 'node:path'
2+ import { spawnSync } from 'node:child_process'
3+ import { mkdtempSync } from 'node:fs'
4+ import { tmpdir } from 'node:os'
5+ import { dirname , join , resolve } from 'node:path'
36import { fileURLToPath } from 'node:url'
47import { loadProfileEnvironment } from '@powersync-community/powergit-core/profile-env'
58
@@ -10,21 +13,18 @@ const BASE_HTTP = `http://${HOST}:${PORT}`
1013const __filename = fileURLToPath ( import . meta. url )
1114const __dirname = dirname ( __filename )
1215const repoRoot = resolve ( __dirname , '..' , '..' , '..' )
13- const profileEnv = loadProfileEnvironment ( {
14- startDir : repoRoot ,
15- updateState : false ,
16- } )
17- const combinedEnv = profileEnv . combinedEnv
1816
1917const isPlaceholder = ( value ?: string | null ) => {
2018 if ( typeof value !== 'string' ) return true
2119 return value . trim ( ) . length === 0
2220}
2321
24- for ( const [ key , value ] of Object . entries ( combinedEnv ) ) {
25- if ( isPlaceholder ( process . env [ key ] ) ) {
26- process . env [ key ] = value
27- }
22+ const ensureE2eHome = ( ) => {
23+ const existing = process . env . POWERGIT_HOME
24+ if ( existing && existing . trim ( ) . length > 0 ) return existing . trim ( )
25+ const dir = mkdtempSync ( join ( tmpdir ( ) , 'powergit-explorer-e2e-' ) )
26+ process . env . POWERGIT_HOME = dir
27+ return dir
2828}
2929
3030const stripQuotes = ( value ?: string ) => {
@@ -35,6 +35,100 @@ const stripQuotes = (value?: string) => {
3535 return value
3636}
3737
38+ function parseExportedEnv ( output : string ) : Record < string , string > {
39+ const env : Record < string , string > = { }
40+ const regex = / ^ e x p o r t \s + ( [ A - Z 0 - 9 _ ] + ) = ( .* ) $ / gm
41+ let match : RegExpExecArray | null = null
42+ while ( ( match = regex . exec ( output ) ) !== null ) {
43+ const key = match [ 1 ]
44+ const rawValue = match [ 2 ]
45+ if ( ! key || rawValue == null ) continue
46+ try {
47+ env [ key ] = JSON . parse ( rawValue ) as string
48+ } catch {
49+ env [ key ] = rawValue . replace ( / ^ " + | " + $ / g, '' )
50+ }
51+ }
52+ return env
53+ }
54+
55+ const ensureLocalStackEnv = ( ) => {
56+ const explicitProfile = ( process . env . STACK_PROFILE ?? '' ) . trim ( )
57+ const desiredProfile = explicitProfile || 'local-dev'
58+ if ( ! explicitProfile ) {
59+ process . env . STACK_PROFILE = desiredProfile
60+ }
61+
62+ if ( desiredProfile === 'local-dev' ) {
63+ ensureE2eHome ( )
64+ }
65+
66+ let profileEnv = loadProfileEnvironment ( {
67+ profile : desiredProfile ,
68+ startDir : repoRoot ,
69+ updateState : false ,
70+ strict : Boolean ( explicitProfile ) ,
71+ } )
72+ const applyCombinedEnv = ( combinedEnv : Record < string , string > ) => {
73+ for ( const [ key , value ] of Object . entries ( combinedEnv ) ) {
74+ if ( isPlaceholder ( process . env [ key ] ) ) {
75+ process . env [ key ] = value
76+ }
77+ }
78+ }
79+ applyCombinedEnv ( profileEnv . combinedEnv )
80+
81+ const requiredLiveEnv = [
82+ 'SUPABASE_URL' ,
83+ 'SUPABASE_ANON_KEY' ,
84+ 'SUPABASE_SERVICE_ROLE_KEY' ,
85+ 'SUPABASE_EMAIL' ,
86+ 'SUPABASE_PASSWORD' ,
87+ 'POWERSYNC_URL' ,
88+ 'POWERSYNC_DAEMON_URL' ,
89+ 'POWERGIT_TEST_REMOTE_URL' ,
90+ ]
91+ const missingLive = requiredLiveEnv . filter ( ( name ) => isPlaceholder ( process . env [ name ] ) )
92+
93+ if ( missingLive . length === 0 || desiredProfile !== 'local-dev' ) {
94+ return
95+ }
96+
97+ const scriptPath = resolve ( repoRoot , 'scripts' , 'dev-local-stack.mjs' )
98+ const result = spawnSync ( process . execPath , [ scriptPath , 'start' , '--print-exports' ] , {
99+ cwd : repoRoot ,
100+ env : { ...process . env , STACK_PROFILE : desiredProfile } ,
101+ encoding : 'utf8' ,
102+ } )
103+ if ( result . status !== 0 ) {
104+ const stderr = typeof result . stderr === 'string' ? result . stderr : ''
105+ throw new Error (
106+ `[playwright] Failed to start local PowerSync stack for live e2e (missing: ${ missingLive . join ( ', ' ) } ).${ stderr ? `\n${ stderr } ` : '' } ` ,
107+ )
108+ }
109+
110+ const exportedEnv = parseExportedEnv ( typeof result . stdout === 'string' ? result . stdout : '' )
111+ for ( const [ key , value ] of Object . entries ( exportedEnv ) ) {
112+ process . env [ key ] = value
113+ }
114+
115+ profileEnv = loadProfileEnvironment ( {
116+ profile : desiredProfile ,
117+ startDir : repoRoot ,
118+ updateState : false ,
119+ strict : Boolean ( explicitProfile ) ,
120+ } )
121+ applyCombinedEnv ( profileEnv . combinedEnv )
122+ }
123+
124+ ensureLocalStackEnv ( )
125+
126+ const profileEnv = loadProfileEnvironment ( {
127+ startDir : repoRoot ,
128+ updateState : false ,
129+ } )
130+ const combinedEnv = profileEnv . combinedEnv
131+
38132const getEnvOrEmpty = ( ...keys : string [ ] ) => {
39133 for ( const key of keys ) {
40134 const value = stripQuotes ( process . env [ key ] )
@@ -53,6 +147,7 @@ const SUPABASE_URL = getEnvOrEmpty('VITE_SUPABASE_URL', 'SUPABASE_URL') || 'http
53147const SUPABASE_ANON_KEY = getEnvOrEmpty ( 'VITE_SUPABASE_ANON_KEY' , 'SUPABASE_ANON_KEY' ) || 'test-anon-key'
54148
55149const TEST_TIMEOUT_MS = 30_000
150+ const LIVE_TEST_TIMEOUT_MS = Number ( process . env . POWERSYNC_E2E_LIVE_TIMEOUT_MS ?? ( process . env . CI ? 600_000 : 120_000 ) )
56151export const BASE_URL = BASE_HTTP
57152
58153export default defineConfig ( {
@@ -82,6 +177,7 @@ export default defineConfig({
82177 } ,
83178 {
84179 name : 'chromium-live' ,
180+ timeout : LIVE_TEST_TIMEOUT_MS ,
85181 use : { ...devices [ 'Desktop Chrome' ] , } ,
86182 testMatch : / t e s t s \/ e 2 e \/ l i v e - .* \. s p e c \. t s / ,
87183 dependencies : [ 'setup-live' ] ,
0 commit comments