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
7 changed files
with
251 additions
and
84 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
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,204 @@ | ||
import {spawn} from 'child_process'; | ||
import type {Server} from 'http'; | ||
import type {AddressInfo} from 'net'; | ||
import type {SampleInfo} from '../demo/src/lib/layout/sample'; | ||
|
||
interface RequestBody { | ||
project?: string; | ||
framework?: string; | ||
sampleKey?: string; | ||
sampleInfo?: Pick<SampleInfo, 'componentName' | 'sampleName' | 'style'>; | ||
} | ||
|
||
const dontReuseExistingServer = process.env.DONT_REUSE_EXISTING_SERVER === 'true'; | ||
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)) { | ||
if (dontReuseExistingServer) { | ||
throw new Error(`Server ${serverKey} is already running and reusing a server is disabled`); | ||
} | ||
// 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 (isWindows) { | ||
proc.kill(); | ||
} 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)); | ||
} | ||
}; | ||
|
||
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 express = await import('express'); | ||
const app = express.default(); | ||
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'); | ||
}; | ||
}; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
Oops, something went wrong.