forked from AmadeusITGroup/AgnosUI
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(e2e): only start servers when needed
Note that this change is also a workaround for: microsoft/playwright#18209
- Loading branch information
Showing
9 changed files
with
452 additions
and
210 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,13 @@ | ||
import setupCoverage from '@agnos-ui/code-coverage/setup'; | ||
import serverManager from './serverManager'; | ||
|
||
async function globalSetup() { | ||
return await setupCoverage(import.meta.dirname); | ||
const coverageTeardown = await setupCoverage(import.meta.dirname); | ||
const serverManagerTeardown = await serverManager(); | ||
return async () => { | ||
await serverManagerTeardown(); | ||
await coverageTeardown(); | ||
}; | ||
} | ||
|
||
export default globalSetup; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
import {spawn} from 'child_process'; | ||
import express from 'express'; | ||
import type {Server} from 'http'; | ||
import type {AddressInfo} from 'net'; | ||
import type {Project, Frameworks, SimpleSampleInfo} from './types'; | ||
|
||
interface RequestBody { | ||
project?: Project; | ||
framework?: Frameworks; | ||
sampleKey?: string; | ||
sampleInfo?: SimpleSampleInfo; | ||
} | ||
|
||
const isPreview = process.env.PREVIEW === 'true'; | ||
const includeCoverage = process.env.COVERAGE === 'true'; | ||
const coverageSuffix = includeCoverage ? ':coverage' : ''; | ||
const previewOrDev = isPreview ? 'preview' : 'dev'; | ||
const allServers = { | ||
angularDemoDevBootstrap: { | ||
command: ['npm', 'run', '-w', 'angular/demo', `dev:bootstrap${coverageSuffix}`], | ||
url: 'http://localhost:4200', | ||
urlReadyPath: '/angular/samples/bootstrap/', | ||
}, | ||
angularDemoDevDaisyui: { | ||
command: ['npm', 'run', '-w', 'angular/demo', `dev:daisyui${coverageSuffix}`], | ||
url: 'http://localhost:4201', | ||
urlReadyPath: '/angular/samples/daisyui/', | ||
}, | ||
reactDemoDev: { | ||
command: ['npm', 'run', '-w', 'react/demo', `dev${coverageSuffix}`], | ||
url: 'http://localhost:3000', | ||
urlReadyPath: '/react/samples/bootstrap/', | ||
}, | ||
svelteDemoDev: { | ||
command: ['npm', 'run', '-w', 'svelte/demo', `dev${coverageSuffix}`], | ||
url: 'http://localhost:3001', | ||
urlReadyPath: '/svelte/samples/bootstrap/', | ||
}, | ||
demoSite: { | ||
command: ['npm', 'run', '-w', 'demo', `${previewOrDev}${coverageSuffix}`], | ||
url: 'http://localhost:4000', | ||
urlReadyPath: '/', | ||
}, | ||
}; | ||
type ServerKey = keyof typeof allServers; | ||
|
||
const getNeededServersAndURL = ({project, framework, sampleInfo}: RequestBody) => { | ||
let url: string | undefined; | ||
const servers: ServerKey[] = []; | ||
switch (project) { | ||
case 'singlebrowser': | ||
case 'main': { | ||
let urlPath = '/'; | ||
if (framework) { | ||
if (sampleInfo) { | ||
urlPath = `/${framework}/samples/${sampleInfo.style}/#/${sampleInfo.componentName}/${sampleInfo.sampleName}`; | ||
} else { | ||
urlPath = `/${framework}/samples/bootstrap/`; | ||
} | ||
} | ||
if (isPreview) { | ||
servers.push('demoSite'); | ||
} else { | ||
switch (framework) { | ||
case 'angular': | ||
if (sampleInfo?.style === 'daisyui') { | ||
servers.push('angularDemoDevDaisyui'); | ||
} else { | ||
servers.push('angularDemoDevBootstrap'); | ||
} | ||
break; | ||
case 'react': | ||
servers.push('reactDemoDev'); | ||
break; | ||
case 'svelte': | ||
servers.push('svelteDemoDev'); | ||
break; | ||
} | ||
} | ||
url = `${allServers[servers[0]].url}${urlPath}`; | ||
break; | ||
} | ||
case 'demo': | ||
servers.push('demoSite'); | ||
if (!isPreview) { | ||
servers.push('angularDemoDevBootstrap', 'angularDemoDevDaisyui', 'reactDemoDev', 'svelteDemoDev'); | ||
} | ||
url = allServers.demoSite.url; | ||
break; | ||
} | ||
return {servers, url}; | ||
}; | ||
|
||
const isURLReady = async (url: string, signal: AbortSignal) => { | ||
const abortController = new AbortController(); | ||
const listener = () => abortController.abort(); | ||
signal.addEventListener('abort', listener); | ||
try { | ||
const res = await fetch(url, {signal: abortController.signal}); | ||
await res.body?.cancel(); | ||
return res.ok; | ||
} catch (error) { | ||
return false; | ||
} finally { | ||
signal.removeEventListener('abort', listener); | ||
} | ||
}; | ||
|
||
const startServer = async (serverKey: ServerKey, abortSignal: AbortSignal) => { | ||
const serverInfo = allServers[serverKey]; | ||
const url = `${serverInfo.url}${serverInfo.urlReadyPath}`; | ||
if (await isURLReady(url, abortSignal)) { | ||
// server is already running | ||
console.log(`Reusing existing server ${serverKey}`); | ||
return; | ||
} | ||
if (abortSignal.aborted) { | ||
return; | ||
} | ||
console.log(`Starting server ${serverKey}`); | ||
const isWindows = process.platform === 'win32'; | ||
let processExited = false; | ||
const proc = spawn(serverInfo.command[0], serverInfo.command.slice(1), { | ||
shell: isWindows, | ||
stdio: 'inherit', | ||
detached: !isWindows, | ||
}); | ||
|
||
const onAbort = () => { | ||
if (proc.pid != null) { | ||
if (isWindows) { | ||
spawn('taskkill', ['/pid', `${proc.pid}`, '/t', '/f'], {stdio: 'inherit'}); | ||
} else if (proc.pid != null) { | ||
process.kill(-proc.pid, 'SIGINT'); | ||
} | ||
} | ||
}; | ||
|
||
abortSignal.addEventListener('abort', onAbort); | ||
|
||
proc.on('close', (code, signal) => { | ||
processExited = true; | ||
abortSignal.removeEventListener('abort', onAbort); | ||
console.error(`Server ${serverKey} exited with ${code ? `code ${code}` : `signal ${signal}`}`); | ||
}); | ||
|
||
proc.on('error', (error) => { | ||
console.error(`Failed to start server ${serverKey}: ${error}`); | ||
}); | ||
let urlReady = false; | ||
while (!urlReady && !abortSignal.aborted) { | ||
if (processExited) { | ||
throw new Error(`Server ${serverKey} exited before being ready`); | ||
} | ||
urlReady = await isURLReady(url, abortSignal); | ||
await new Promise((resolve) => setTimeout(resolve, 100)); | ||
} | ||
console.log(`Server ${serverKey} is ready`); | ||
}; | ||
|
||
export default async () => { | ||
const serversStatus = new Map<ServerKey, Promise<void>>(); | ||
const abortController = new AbortController(); | ||
|
||
const ensureServerRuns = async (serverKey: ServerKey) => { | ||
let status = serversStatus.get(serverKey); | ||
if (!status) { | ||
status = startServer(serverKey, abortController.signal); | ||
serversStatus.set(serverKey, status); | ||
} | ||
return await status; | ||
}; | ||
|
||
const app = express(); | ||
app.use(express.json()); | ||
|
||
app.post('/', async (req, res) => { | ||
try { | ||
const {servers, url} = getNeededServersAndURL(req.body); | ||
await Promise.all(servers.map(ensureServerRuns)); | ||
res.json({url}); | ||
} catch (error) { | ||
res.status(500).json({error: `${error}`}); | ||
} | ||
}); | ||
|
||
const server = await new Promise<Server>((resolve, reject) => { | ||
const server = app.listen(0, '127.0.0.1', () => resolve(server)).on('error', reject); | ||
}); | ||
const port = (server.address() as AddressInfo).port; | ||
const serverManagerURL = `http://127.0.0.1:${port}`; | ||
process.env.SERVER_MANAGER_URL = serverManagerURL; | ||
console.log(`Server manager was started on ${serverManagerURL}`); | ||
return async () => { | ||
abortController.abort(); | ||
console.log('Closing server manager...'); | ||
await new Promise((resolve) => { | ||
server.close(resolve); | ||
server.closeAllConnections(); | ||
}); | ||
console.log('Server manager closed'); | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import type {SampleInfo} from '../demo/src/lib/layout/sample'; | ||
export type {Frameworks} from '../demo/src/lib/stores'; | ||
export type SimpleSampleInfo = Pick<SampleInfo, 'style' | 'componentName' | 'sampleName'>; | ||
export type Project = 'singlebrowser' | 'main' | 'demo'; |
Oops, something went wrong.