diff --git a/.github/workflows/validate-os.yml b/.github/workflows/validate-os.yml index 11eeb815f8..61891cf9c2 100644 --- a/.github/workflows/validate-os.yml +++ b/.github/workflows/validate-os.yml @@ -17,3 +17,5 @@ jobs: - run: npm ci - run: npm run build:ci - run: npm run -w verdaccio verdaccio-publish + - run: npx playwright install --with-deps chromium + - run: npm run e2e singlebrowser chromium svelte samplesMarkup diff --git a/e2e/fixture.ts b/e2e/fixture.ts index 5483fed359..20c136d702 100644 --- a/e2e/fixture.ts +++ b/e2e/fixture.ts @@ -1,18 +1,19 @@ import reportCoverage from '@agnos-ui/code-coverage/reportCoverage'; -import {getSamplesList} from '../demo/scripts/listSamples.plugin'; import {test as base} from '@playwright/test'; -import type {SampleInfo} from '../demo/src/lib/layout/sample'; +import {getSamplesList} from '../demo/scripts/listSamples.plugin'; +import type {Frameworks, Project, SimpleSampleInfo} from './types'; export {expect} from '@playwright/test'; export type FixtureOptions = { // with option: true - framework?: string; + project?: Project; + framework?: Frameworks; sampleKey?: string; // with option: false (default) coverage: void; - sampleInfo?: Pick & {sampleURL: string}; + sampleInfo?: SimpleSampleInfo; }; let cachedSamplesList: undefined | ReturnType; @@ -23,25 +24,30 @@ export const samplesList = () => { return cachedSamplesList; }; +const serverManagerURL = process.env.SERVER_MANAGER_URL!; + export const test = base.extend({ + project: [undefined, {option: true}], framework: [undefined, {option: true}], sampleKey: [undefined, {option: true}], - sampleInfo: async ({sampleKey, baseURL}, use) => { - let sampleInfo: FixtureOptions['sampleInfo']; - if (sampleKey && baseURL) { - const sampleItem = samplesList()[sampleKey]; - if (sampleItem) { - const sampleURL = new URL( - sampleItem.style === 'bootstrap' - ? `#/${sampleItem.componentName}/${sampleItem.sampleName}` - : `../${sampleItem.style}/#/${sampleItem.componentName}/${sampleItem.sampleName}`, - baseURL, - ).href; - sampleInfo = {...sampleItem, sampleURL}; - } - } - await use(sampleInfo); + sampleInfo: async ({sampleKey}, use) => { + await use(sampleKey ? samplesList()[sampleKey] : undefined); }, + baseURL: [ + async ({project, framework, sampleKey, sampleInfo}, use) => { + const req = await fetch(serverManagerURL, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({project, framework, sampleKey, sampleInfo}), + }); + const answer = await req.json(); + if (!req.ok) { + throw new Error(answer.error); + } + await use(answer.url); + }, + {timeout: 300000, scope: 'test'}, + ], coverage: [ async ({page, browserName}, use) => { await use(); diff --git a/e2e/global-setup.ts b/e2e/global-setup.ts index d11c48d054..9a868652ab 100644 --- a/e2e/global-setup.ts +++ b/e2e/global-setup.ts @@ -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; diff --git a/e2e/samplesMarkup.singlebrowser-e2e-spec.ts b/e2e/samplesMarkup.singlebrowser-e2e-spec.ts index c6d7520278..9743cd6f01 100644 --- a/e2e/samplesMarkup.singlebrowser-e2e-spec.ts +++ b/e2e/samplesMarkup.singlebrowser-e2e-spec.ts @@ -30,8 +30,8 @@ test.describe(`Samples markup consistency check`, async () => { test.fixme(sampleKey === 'daisyui/rating/default', 'This sample does not currently have a consistent markup!'); - test(`should have a consistent markup`, async ({page, sampleInfo}) => { - await page.goto(`${sampleInfo?.sampleURL}${samplesExtraHash[sampleKey] ?? ''}`, {waitUntil: 'networkidle'}); + test(`should have a consistent markup`, async ({page, baseURL}) => { + await page.goto(`${baseURL}${samplesExtraHash[sampleKey] ?? ''}`, {waitUntil: 'networkidle'}); await expect.poll(async () => (await page.locator('#root').innerHTML()).trim().length).toBeGreaterThan(0); await samplesExtraAction[sampleKey]?.(page); expect(await htmlSnapshot(page.locator('body'))).toMatchSnapshot(`${sampleKey}.html`); diff --git a/e2e/serverManager.ts b/e2e/serverManager.ts new file mode 100644 index 0000000000..056f36e394 --- /dev/null +++ b/e2e/serverManager.ts @@ -0,0 +1,210 @@ +import {spawn} from 'child_process'; +import express from 'express'; +import type {Server} from 'http'; +import type {AddressInfo} from 'net'; +import type {Frameworks, Project, 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); + + const procClose = new Promise((resolve) => + proc.on('close', (code, signal) => { + processExited = true; + abortSignal.removeEventListener('abort', onAbort); + console.error(`Server ${serverKey} exited with ${code ? `code ${code}` : `signal ${signal}`}`); + resolve(); + }), + ); + + 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`); + return { + exited: procClose, + }; +}; + +export default async () => { + const serversStatus = new Map}>>(); + 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); + } + 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((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(); + }); + await Promise.all([...serversStatus.values()].map(async (status) => await (await status)?.exited)); + console.log('Server manager closed'); + }; +}; diff --git a/e2e/types.ts b/e2e/types.ts new file mode 100644 index 0000000000..955a50fb02 --- /dev/null +++ b/e2e/types.ts @@ -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; +export type Project = 'singlebrowser' | 'main' | 'demo'; diff --git a/package-lock.json b/package-lock.json index a9ab6cfcb6..9b3572e92e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jsdoc": "^48.2.2", + "express": "^4.19.2", "glob": "10.3.12", "husky": "^9.0.11", "lint-staged": "^15.2.2", @@ -230,60 +231,6 @@ "typescript": "~5.4.3" } }, - "angular/ssr-app/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "angular/ssr-app/node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "angular/ssr-app/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "base-po": { "name": "@agnos-ui/base-po", "version": "0.0.0", @@ -7521,6 +7468,63 @@ "url": "https://opencollective.com/verdaccio" } }, + "node_modules/@verdaccio/middleware/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@verdaccio/middleware/node_modules/express": { + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", + "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/@verdaccio/middleware/node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, "node_modules/@verdaccio/middleware/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -7540,6 +7544,11 @@ "node": ">=4.0.0" } }, + "node_modules/@verdaccio/middleware/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/@verdaccio/search-indexer": { "version": "7.0.0-next-7.2", "resolved": "https://registry.npmjs.org/@verdaccio/search-indexer/-/search-indexer-7.0.0-next-7.2.tgz", @@ -11753,16 +11762,16 @@ "dev": true }, "node_modules/express": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", - "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -11798,14 +11807,6 @@ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.5.1.tgz", "integrity": "sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg==" }, - "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -22145,6 +22146,63 @@ "node": ">= 6.0.0" } }, + "node_modules/verdaccio-audit/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/verdaccio-audit/node_modules/express": { + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", + "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/verdaccio-audit/node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, "node_modules/verdaccio-audit/node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -22157,6 +22215,11 @@ "node": ">= 6" } }, + "node_modules/verdaccio-audit/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/verdaccio-htpasswd": { "version": "12.0.0-next-7.13", "resolved": "https://registry.npmjs.org/verdaccio-htpasswd/-/verdaccio-htpasswd-12.0.0-next-7.13.tgz", @@ -23364,60 +23427,6 @@ "vite": "^5.2.7" } }, - "react/ssr-app/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "react/ssr-app/node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "react/ssr-app/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "style-bootstrap": { "name": "@agnos-ui/style-bootstrap", "version": "0.0.0", @@ -23551,6 +23560,63 @@ "verdaccio": "^5.30.2" } }, + "verdaccio/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "verdaccio/node_modules/express": { + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", + "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "verdaccio/node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, "verdaccio/node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -23567,6 +23633,11 @@ "node": ">=12" } }, + "verdaccio/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "verdaccio/node_modules/verdaccio": { "version": "5.30.2", "resolved": "https://registry.npmjs.org/verdaccio/-/verdaccio-5.30.2.tgz", diff --git a/package.json b/package.json index 0aa1b7ab7d..3abc21979f 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jsdoc": "^48.2.2", + "express": "^4.19.2", "glob": "10.3.12", "husky": "^9.0.11", "lint-staged": "^15.2.2", @@ -128,7 +129,8 @@ "command": "node scripts/e2e.js --ui", "dependencies": [ "./code-coverage:build" - ] + ], + "service": true }, "prepare": { "dependencies": [ diff --git a/playwright.config.ts b/playwright.config.ts index b4a55ab732..107e49370e 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,13 +1,14 @@ import type {PlaywrightTestConfig} from '@playwright/test'; import {devices} from '@playwright/test'; -import type {FixtureOptions} from 'e2e/fixture'; import path from 'path'; +import type {FixtureOptions} from './e2e/fixture'; +import type {Frameworks, Project} from './e2e/types'; const isCI = process.env.CI === 'true'; -const isPreview = isCI || process.env.PREVIEW === 'true'; -const includeCoverage = isCI || process.env.COVERAGE === 'true'; -const coverageSuffix = includeCoverage ? ':coverage' : ''; -const previewOrDev = isPreview ? 'preview' : 'dev'; +if (isCI) { + process.env.COVERAGE = 'true'; + process.env.PREVIEW = 'true'; +} const allBrowsers = { chromium: devices['Desktop Chrome'], @@ -32,63 +33,15 @@ const processEnvList = (name: string, valuesMap: Record['projects'] = []; -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: '/', - }, -}; - -const selectedServers = new Set(); -const addServer = (server: keyof typeof allServers) => { - selectedServers.add(server); - return allServers[server].url; +const allFrameworks: Record = { + angular: {}, + react: {}, + svelte: {}, }; -const allFrameworks = { - angular: { - samplesDevServer: () => { - addServer('angularDemoDevBootstrap'); - addServer('angularDemoDevDaisyui'); - return addServer('demoSite'); - }, - }, - react: { - samplesDevServer: () => addServer('reactDemoDev'), - }, - svelte: { - samplesDevServer: () => addServer('svelteDemoDev'), - }, -}; - -const addSamplesServer = (framework: keyof typeof allFrameworks) => - `${isPreview ? addServer('demoSite') : allFrameworks[framework].samplesDevServer()}/${framework}/samples/bootstrap/`; - -const allProjects = { +const allProjects: Record void> = { main: () => { (selectedFrameworks.filteredList ?? selectedFrameworks.fullList).forEach((framework) => { - const baseURL = addSamplesServer(framework); (selectedBrowsers.filteredList ?? selectedBrowsers.fullList).forEach((browser) => { playwrightProjects.push({ name: `main:${framework}:${browser}`, @@ -96,7 +49,7 @@ const allProjects = { use: { ...allBrowsers[browser], framework, - baseURL, + project: 'main', }, }); }); @@ -104,28 +57,27 @@ const allProjects = { }, singlebrowser: () => { (selectedFrameworks.filteredList ?? selectedFrameworks.fullList).forEach((framework) => { - const baseURL = addSamplesServer(framework); (selectedBrowsers.filteredList ?? ['chromium']).forEach((browser) => { playwrightProjects.push({ name: `singleBrowser:${framework}:${browser}`, testMatch: '**/*.singlebrowser-e2e-spec.ts', use: { ...allBrowsers[browser], - baseURL, + framework, + project: 'singlebrowser', }, }); }); }); }, demo: () => { - const baseURL = addServer('demoSite'); (selectedBrowsers.filteredList ?? ['chromium']).forEach((browser) => { playwrightProjects.push({ name: `demo:${browser}`, testMatch: '**/*.demo-e2e-spec.ts', use: { ...allBrowsers[browser], - baseURL, + project: 'demo', }, }); }); @@ -150,9 +102,5 @@ const config: PlaywrightTestConfig = { video: 'on-first-retry', }, projects: playwrightProjects, - webServer: [...selectedServers].map((server) => { - const {command, url, urlReadyPath} = allServers[server]; - return {command, url: `${url}${urlReadyPath}`, reuseExistingServer: !isCI}; - }), }; export default config;