From 28ed38906d07089cea122d2204b73fb63fa9e4d2 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 26 Jul 2026 10:00:24 +0200 Subject: [PATCH] test(perf): add production renderer benchmark build --- apps/electron-backend-e2e/project.json | 9 +- .../performance-build-config.spec.ts | 173 ++++++++++++++++++ apps/electron-backend/project.json | 26 +++ apps/remote-control-web/project.json | 11 ++ apps/web/project.json | 27 +++ 5 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 apps/electron-backend-e2e/src/performance/performance-build-config.spec.ts diff --git a/apps/electron-backend-e2e/project.json b/apps/electron-backend-e2e/project.json index 1918e34fa..51ed910a8 100644 --- a/apps/electron-backend-e2e/project.json +++ b/apps/electron-backend-e2e/project.json @@ -12,6 +12,13 @@ "e2e": { "dependsOn": ["electron-backend:build-e2e"] }, + "test-performance-harness": { + "executor": "nx:run-commands", + "options": { + "cwd": "apps/electron-backend-e2e", + "command": "pnpm exec tsx --test src/performance/*.spec.ts" + } + }, "benchmark-m3u-refresh-cancellation": { "dependsOn": ["electron-backend:build-e2e"], "executor": "nx:run-commands", @@ -19,7 +26,7 @@ "parallelism": false, "options": { "cwd": "apps/electron-backend-e2e", - "command": "pnpm exec playwright test --config=playwright.performance.config.ts" + "command": "pnpm exec playwright test --config=playwright.performance.config.ts src/m3u-refresh-cancellation.performance.ts" } }, "packaged-frame-copy-smoke": { diff --git a/apps/electron-backend-e2e/src/performance/performance-build-config.spec.ts b/apps/electron-backend-e2e/src/performance/performance-build-config.spec.ts new file mode 100644 index 000000000..c333d54ab --- /dev/null +++ b/apps/electron-backend-e2e/src/performance/performance-build-config.spec.ts @@ -0,0 +1,173 @@ +/* eslint-disable playwright/expect-expect -- These are Node assertion-based configuration contract tests. */ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; +import { join } from 'node:path'; + +interface TargetConfiguration { + configurations?: Record>; + dependsOn?: unknown; + executor?: unknown; + options?: Record; +} + +interface ProjectConfiguration { + targets: Record; +} + +const workspaceRoot = fileURLToPath(new URL('../../../../', import.meta.url)); + +function readProject(relativePath: string): ProjectConfiguration { + return JSON.parse( + readFileSync(join(workspaceRoot, relativePath), 'utf8') + ) as ProjectConfiguration; +} + +const e2eProject = readProject('apps/electron-backend-e2e/project.json'); +const electronProject = readProject('apps/electron-backend/project.json'); +const remoteControlProject = readProject( + 'apps/remote-control-web/project.json' +); +const webProject = readProject('apps/web/project.json'); + +test('the performance harness target runs only Node performance specs', () => { + const target = e2eProject.targets['test-performance-harness']; + + assert.ok( + target, + 'electron-backend-e2e must define test-performance-harness' + ); + assert.equal(target.executor, 'nx:run-commands'); + assert.equal(target.options?.['cwd'], 'apps/electron-backend-e2e'); + assert.equal( + target.options?.['command'], + 'pnpm exec tsx --test src/performance/*.spec.ts' + ); +}); + +test('the web performance build keeps production renderer behavior with profiling source maps', () => { + const build = webProject.targets['build']; + const production = build.configurations?.['production']; + const performance = build.configurations?.['electron-performance']; + + assert.ok(production, 'web:build must define production'); + assert.ok(performance, 'web:build must define electron-performance'); + assert.equal(performance['baseHref'], './'); + assert.equal(performance['serviceWorker'], false); + assert.deepEqual(performance['optimization'], production['optimization']); + assert.equal(performance['outputHashing'], production['outputHashing']); + assert.deepEqual( + performance['fileReplacements'], + production['fileReplacements'] + ); + assert.equal(performance['sourceMap'], true); +}); + +test('the remote-control performance build keeps optimized hashed output with profiling source maps', () => { + const build = remoteControlProject.targets['build']; + const production = build.configurations?.['production']; + const performance = build.configurations?.['electron-performance']; + + assert.ok(production, 'remote-control-web:build must define production'); + assert.ok( + performance, + 'remote-control-web:build must define electron-performance' + ); + assert.equal(performance['optimization'], true); + assert.equal(performance['outputHashing'], production['outputHashing']); + assert.equal(performance['sourceMap'], true); +}); + +test('the Electron performance build keeps production main-process behavior with profiling source maps', () => { + const build = electronProject.targets['build']; + const production = build.configurations?.['production']; + const performance = build.configurations?.['electron-performance']; + + assert.ok(production, 'electron-backend:build must define production'); + assert.ok( + performance, + 'electron-backend:build must define electron-performance' + ); + assert.equal(performance['optimization'], production['optimization']); + assert.equal(performance['inspect'], production['inspect']); + assert.deepEqual( + performance['fileReplacements'], + production['fileReplacements'] + ); + assert.equal(performance['sourceMap'], true); +}); + +test('the regular Electron build keeps its existing renderer dependency contract', () => { + const build = electronProject.targets['build']; + const dependencies = build.dependsOn as Array< + Record | string + >; + const rendererBuild = dependencies.find( + (dependency): dependency is Record => + typeof dependency === 'object' && + dependency !== null && + dependency['target'] === 'build' + ); + + assert.deepEqual(rendererBuild, { + projects: ['web', 'remote-control-web'], + target: 'build', + }); +}); + +test('the web performance wrapper selects the profiling configuration', () => { + const target = webProject.targets['build-performance']; + + assert.ok(target, 'web must define build-performance'); + assert.equal(target.executor, 'nx:run-commands'); + assert.equal( + target.options?.['command'], + 'pnpm nx run web:build:electron-performance' + ); +}); + +test('the remote-control performance wrapper selects the profiling configuration', () => { + const target = remoteControlProject.targets['build-performance']; + + assert.ok(target, 'remote-control-web must define build-performance'); + assert.equal(target.executor, 'nx:run-commands'); + assert.equal( + target.options?.['command'], + 'pnpm nx run remote-control-web:build:electron-performance' + ); +}); + +test('the Electron performance wrapper owns build dependencies before its profiled build', () => { + const target = electronProject.targets['build-performance']; + + assert.ok(target, 'electron-backend must define build-performance'); + assert.equal(target.executor, 'nx:run-commands'); + assert.deepEqual(target.dependsOn, [ + 'electron-backend:build-worker', + 'electron-backend:build-embedded-mpv', + { + projects: ['web', 'remote-control-web'], + target: 'build-performance', + }, + ]); + assert.equal( + target.options?.['command'], + 'pnpm nx run electron-backend:build:electron-performance --excludeTaskDependencies' + ); +}); + +test('the cancellation benchmark keeps its existing E2E build dependency', () => { + const target = e2eProject.targets['benchmark-m3u-refresh-cancellation']; + + assert.deepEqual(target.dependsOn, ['electron-backend:build-e2e']); +}); + +test('the cancellation benchmark command is pinned to its Playwright test file', () => { + const target = e2eProject.targets['benchmark-m3u-refresh-cancellation']; + + assert.equal( + target.options?.['command'], + 'pnpm exec playwright test --config=playwright.performance.config.ts src/m3u-refresh-cancellation.performance.ts' + ); +}); diff --git a/apps/electron-backend/project.json b/apps/electron-backend/project.json index 8e6b9febb..37b1e7e07 100644 --- a/apps/electron-backend/project.json +++ b/apps/electron-backend/project.json @@ -81,9 +81,35 @@ "with": "apps/electron-backend/src/environments/environment.prod.ts" } ] + }, + "electron-performance": { + "optimization": true, + "extractLicenses": true, + "inspect": false, + "sourceMap": true, + "fileReplacements": [ + { + "replace": "apps/electron-backend/src/environments/environment.ts", + "with": "apps/electron-backend/src/environments/environment.prod.ts" + } + ] } } }, + "build-performance": { + "dependsOn": [ + "electron-backend:build-worker", + "electron-backend:build-embedded-mpv", + { + "projects": ["web", "remote-control-web"], + "target": "build-performance" + } + ], + "executor": "nx:run-commands", + "options": { + "command": "pnpm nx run electron-backend:build:electron-performance --excludeTaskDependencies" + } + }, "build-e2e": { "dependsOn": [ "electron-backend:build-worker", diff --git a/apps/remote-control-web/project.json b/apps/remote-control-web/project.json index 5819a8bf5..8c65582c8 100644 --- a/apps/remote-control-web/project.json +++ b/apps/remote-control-web/project.json @@ -49,6 +49,11 @@ ], "outputHashing": "all" }, + "electron-performance": { + "optimization": true, + "outputHashing": "all", + "sourceMap": true + }, "development": { "optimization": false, "extractLicenses": false, @@ -63,6 +68,12 @@ "command": "pnpm nx run remote-control-web:build:development" } }, + "build-performance": { + "executor": "nx:run-commands", + "options": { + "command": "pnpm nx run remote-control-web:build:electron-performance" + } + }, "serve": { "continuous": true, "executor": "@angular/build:dev-server", diff --git a/apps/web/project.json b/apps/web/project.json index f7b179709..3fb7392b3 100644 --- a/apps/web/project.json +++ b/apps/web/project.json @@ -122,6 +122,27 @@ "extractLicenses": false, "sourceMap": true }, + "electron-performance": { + "baseHref": "./", + "serviceWorker": false, + "optimization": { + "scripts": true, + "styles": { + "minify": true, + "inlineCritical": false, + "removeSpecialComments": true + }, + "fonts": true + }, + "outputHashing": "all", + "sourceMap": true, + "fileReplacements": [ + { + "replace": "apps/web/src/environments/environment.ts", + "with": "apps/web/src/environments/environment.prod.ts" + } + ] + }, "electron-e2e": { "baseHref": "./", "serviceWorker": false, @@ -138,6 +159,12 @@ "command": "pnpm nx run web:build:electron-e2e" } }, + "build-performance": { + "executor": "nx:run-commands", + "options": { + "command": "pnpm nx run web:build:electron-performance" + } + }, "serve": { "continuous": true, "executor": "@angular/build:dev-server",