mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(e2e): stop the web backend from outliving Playwright runs (#1747)
This commit is contained in:
1 parent
d60c80746b
commit
4e17fbed4e
6 files changed
+107
-11
No files matched your search
@@ -0,0 +1,63 @@
|
|||||||
|
import { readdirSync, readFileSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
describe('Playwright web-backend launch', () => {
|
||||||
|
const appsDirectory = join(process.cwd(), 'apps');
|
||||||
|
const launchCommand = 'node --import tsx apps/web-backend/src/main.ts';
|
||||||
|
const configsStartingBackend = ['apps/web-e2e/playwright.config.ts'];
|
||||||
|
const readSource = (path: string) =>
|
||||||
|
readFileSync(join(process.cwd(), path), 'utf8');
|
||||||
|
const serveEnv = (
|
||||||
|
JSON.parse(readSource('apps/web-backend/project.json')) as {
|
||||||
|
targets: { serve: { options: { env: Record<string, string> } } };
|
||||||
|
}
|
||||||
|
).targets.serve.options.env;
|
||||||
|
|
||||||
|
it('knows every Playwright config that starts the web backend', () => {
|
||||||
|
const configs = readdirSync(appsDirectory, { withFileTypes: true })
|
||||||
|
.filter((entry) => entry.isDirectory())
|
||||||
|
.flatMap((entry) =>
|
||||||
|
readdirSync(join(appsDirectory, entry.name))
|
||||||
|
.filter((name) =>
|
||||||
|
/^playwright(\..+)?\.config\.ts$/.test(name)
|
||||||
|
)
|
||||||
|
.map((name) => `apps/${entry.name}/${name}`)
|
||||||
|
)
|
||||||
|
.filter((configPath) =>
|
||||||
|
readSource(configPath).includes('web-backend')
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(configs.sort()).toEqual(configsStartingBackend);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nx starts a run-commands child in its own detached process group, and
|
||||||
|
// Playwright stops a webServer with a process-group SIGKILL, so a backend
|
||||||
|
// launched through Nx outlived the run and kept port 3333.
|
||||||
|
it.each(configsStartingBackend)(
|
||||||
|
'starts the backend in %s as a single node process',
|
||||||
|
(configPath) => {
|
||||||
|
const source = readSource(configPath);
|
||||||
|
const start = source.indexOf(`'${launchCommand}'`);
|
||||||
|
|
||||||
|
expect(source).not.toMatch(/\bnx\b[^'"`\n]*web-backend/);
|
||||||
|
expect(start).toBeGreaterThan(-1);
|
||||||
|
expect(
|
||||||
|
source.indexOf(launchCommand, start + launchCommand.length)
|
||||||
|
).toBe(-1);
|
||||||
|
|
||||||
|
// WEB_BACKEND_PORT relocates the run; its default is the target's.
|
||||||
|
const { PORT: port, ...fixedEnv } = serveEnv;
|
||||||
|
const entry = source.slice(start, source.indexOf('url:', start));
|
||||||
|
for (const [name, value] of Object.entries(fixedEnv)) {
|
||||||
|
expect(entry).toContain(`${name}: '${value}'`);
|
||||||
|
}
|
||||||
|
expect(source).toContain(
|
||||||
|
`process.env['WEB_BACKEND_PORT'] ?? '${port}'`
|
||||||
|
);
|
||||||
|
expect(entry).toContain('PORT: webBackendPort,');
|
||||||
|
expect(source).toContain(
|
||||||
|
'url: `http://localhost:${webBackendPort}/health`'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -15,6 +15,7 @@ const webServerCommand =
|
|||||||
? `pnpm nx run web:serve-static --port=${staticPwaPort}`
|
? `pnpm nx run web:serve-static --port=${staticPwaPort}`
|
||||||
: 'pnpm nx run web:serve';
|
: 'pnpm nx run web:serve';
|
||||||
const reuseExistingWebServer = isStaticPwaE2E ? false : !process.env['CI'];
|
const reuseExistingWebServer = isStaticPwaE2E ? false : !process.env['CI'];
|
||||||
|
const webBackendPort = process.env['WEB_BACKEND_PORT'] ?? '3333';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read environment variables from file.
|
* Read environment variables from file.
|
||||||
@@ -57,7 +58,8 @@ export default defineConfig({
|
|||||||
* both mock servers honour them as a fallback for PORT (their serve
|
* both mock servers honour them as a fallback for PORT (their serve
|
||||||
* targets no longer pin PORT, so an explicit shell value reaches the
|
* targets no longer pin PORT, so an explicit shell value reaches the
|
||||||
* process). That is what lets two worktrees run E2E side by side when one
|
* process). That is what lets two worktrees run E2E side by side when one
|
||||||
* already holds 3210/3211.
|
* already holds 3210/3211. WEB_BACKEND_PORT does the same for the web
|
||||||
|
* backend and self-hosted.e2e.ts, which is the only spec that calls it.
|
||||||
*/
|
*/
|
||||||
webServer: [
|
webServer: [
|
||||||
{
|
{
|
||||||
@@ -89,9 +91,18 @@ export default defineConfig({
|
|||||||
reuseExistingServer: !process.env['CI'],
|
reuseExistingServer: !process.env['CI'],
|
||||||
cwd: workspaceRoot,
|
cwd: workspaceRoot,
|
||||||
},
|
},
|
||||||
|
/* Same single-process launch for the backend; `env` mirrors the
|
||||||
|
* `web-backend:serve` target, which stays the manual entry point. */
|
||||||
{
|
{
|
||||||
command: 'pnpm nx run web-backend:serve',
|
command: 'node --import tsx apps/web-backend/src/main.ts',
|
||||||
url: 'http://localhost:3333/health',
|
env: {
|
||||||
|
PORT: webBackendPort,
|
||||||
|
CLIENT_URL: 'http://localhost:4200',
|
||||||
|
BACKEND_URL: '/api',
|
||||||
|
IPTVNATOR_PROXY_ALLOW_PRIVATE_NETWORKS: '1',
|
||||||
|
TSX_TSCONFIG_PATH: 'tsconfig.base.json',
|
||||||
|
},
|
||||||
|
url: `http://localhost:${webBackendPort}/health`,
|
||||||
reuseExistingServer: !process.env['CI'],
|
reuseExistingServer: !process.env['CI'],
|
||||||
cwd: workspaceRoot,
|
cwd: workspaceRoot,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { openSourceEditor, sourceRowByTitle } from './sources-pwa.helpers';
|
|||||||
import { postWithRetry, setInputValue } from './e2e-helpers';
|
import { postWithRetry, setInputValue } from './e2e-helpers';
|
||||||
import { expect, test } from './fixtures';
|
import { expect, test } from './fixtures';
|
||||||
|
|
||||||
const WEB_BACKEND_URL = 'http://localhost:3333';
|
const WEB_BACKEND_PORT = process.env['WEB_BACKEND_PORT'] ?? '3333';
|
||||||
|
const WEB_BACKEND_URL = `http://localhost:${WEB_BACKEND_PORT}`;
|
||||||
const XTREAM_MOCK_PORT = process.env['XTREAM_MOCK_PORT'] ?? '3211';
|
const XTREAM_MOCK_PORT = process.env['XTREAM_MOCK_PORT'] ?? '3211';
|
||||||
const STALKER_MOCK_PORT = process.env['MOCK_PORT'] ?? '3210';
|
const STALKER_MOCK_PORT = process.env['MOCK_PORT'] ?? '3210';
|
||||||
const XTREAM_MOCK_SERVER = `http://localhost:${XTREAM_MOCK_PORT}`;
|
const XTREAM_MOCK_SERVER = `http://localhost:${XTREAM_MOCK_PORT}`;
|
||||||
|
|||||||
@@ -70,17 +70,20 @@ describe('Playwright mock-server launch', () => {
|
|||||||
const source = readSource(configPath);
|
const source = readSource(configPath);
|
||||||
|
|
||||||
expect(source).not.toMatch(/\bnx\b[^'"`\n]*mock-server/);
|
expect(source).not.toMatch(/\bnx\b[^'"`\n]*mock-server/);
|
||||||
|
// Other single-process webServers (web-e2e's web-backend) also set
|
||||||
|
// TSX_TSCONFIG_PATH, so check it inside each mock's own entry.
|
||||||
for (const mock of mocks) {
|
for (const mock of mocks) {
|
||||||
expect(source).toContain(
|
const launch = `'node --import tsx apps/${mock}-mock-server/src/main.ts'`;
|
||||||
`'node --import tsx apps/${mock}-mock-server/src/main.ts'`
|
const start = source.indexOf(launch);
|
||||||
);
|
|
||||||
|
expect(start).toBeGreaterThan(-1);
|
||||||
|
expect(
|
||||||
|
source.slice(start, source.indexOf('url:', start))
|
||||||
|
).toContain("TSX_TSCONFIG_PATH: 'tsconfig.base.json'");
|
||||||
}
|
}
|
||||||
expect(
|
expect(
|
||||||
source.match(/node --import tsx apps\/[\w-]+-mock-server\//g)
|
source.match(/node --import tsx apps\/[\w-]+-mock-server\//g)
|
||||||
).toHaveLength(mocks.length);
|
).toHaveLength(mocks.length);
|
||||||
expect(
|
|
||||||
source.match(/TSX_TSCONFIG_PATH: 'tsconfig\.base\.json'/g)
|
|
||||||
).toHaveLength(mocks.length);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -73,6 +73,23 @@ The current self-hosted PWA uses these `apps/web-backend` routes:
|
|||||||
- `GET /xtream?targetId=<id>&username=<u>&password=<p>&action=<action>`
|
- `GET /xtream?targetId=<id>&username=<u>&password=<p>&action=<action>`
|
||||||
- `GET /stalker?targetId=<id>&macAddress=<mac>&action=<action>`
|
- `GET /stalker?targetId=<id>&macAddress=<mac>&action=<action>`
|
||||||
|
|
||||||
|
`pnpm nx run web-backend:serve` starts the backend by hand on port 3333 with
|
||||||
|
`TSX_TSCONFIG_PATH=tsconfig.base.json`, which `tsx` needs for the
|
||||||
|
`@iptvnator/*` path aliases. `apps/web-e2e/playwright.config.ts` does not use
|
||||||
|
that target: it launches `node --import tsx apps/web-backend/src/main.ts` with
|
||||||
|
an `env` that mirrors the target's. Nx `run-commands` starts its command in a
|
||||||
|
detached process group, and Playwright stops a `webServer` with a
|
||||||
|
process-group `SIGKILL` (`taskkill /T /F` on Windows). The kill therefore
|
||||||
|
reached only `pnpm`/`nx`, and the orphaned server kept port 3333 after the run.
|
||||||
|
`apps/web-backend/src/app/project-config.spec.ts` fails if a Playwright config
|
||||||
|
launches the backend through Nx or its `env` drifts from the `serve` target.
|
||||||
|
`WEB_BACKEND_PORT` (default 3333) relocates the E2E backend and the
|
||||||
|
`self-hosted.e2e.ts` URLs, like `MOCK_PORT` does for the mocks. Use it when
|
||||||
|
another worktree holds 3333. The `web:serve` entry can stay on Nx:
|
||||||
|
`@angular/build:dev-server` runs inside the Nx process, so the group kill
|
||||||
|
stops it. The mock servers follow the same
|
||||||
|
rule; see [Xtream mock Playwright integration](xtream-mock-server.md#playwright-integration).
|
||||||
|
|
||||||
The PWA continues to use `PwaService`; only the backend base URL is resolved at
|
The PWA continues to use `PwaService`; only the backend base URL is resolved at
|
||||||
runtime. Electron routes remain owned by the Electron backend and preload
|
runtime. Electron routes remain owned by the Electron backend and preload
|
||||||
bridge.
|
bridge.
|
||||||
|
|||||||
@@ -579,7 +579,8 @@ for the serve target's `--tsconfig` flag and is required for the `@iptvnator/*`
|
|||||||
path aliases. `project-config.spec.ts` pins which configs start which mock,
|
path aliases. `project-config.spec.ts` pins which configs start which mock,
|
||||||
and fails if any of them launches a mock through Nx or a new config starts one
|
and fails if any of them launches a mock through Nx or a new config starts one
|
||||||
without being listed there. The `serve` targets remain the entry point
|
without being listed there. The `serve` targets remain the entry point
|
||||||
for starting a mock by hand.
|
for starting a mock by hand. The web-e2e `web-backend` entry uses the same
|
||||||
|
launch form; see [PWA web backend](pwa-self-hosted.md#web-backend).
|
||||||
|
|
||||||
### Request Interception
|
### Request Interception
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user