1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-07-03 20:45:52 +01:00
Files
frontend/build-scripts/gulp/rspack.js
Bram Kragten 2c8d6c1a02 Add Playwright e2e tests for demo, test app, and gallery
Adds Playwright end-to-end tests covering three targets:
- the demo build
- a new lightweight test app exercising several scenarios (theming,
  admin/non-admin sidebar, panel navigation, more-info dialog)
- the component gallery

Includes the gulp/rspack build infra for the test app and an "E2E Tests"
GitHub Actions workflow that builds each target once, shares it via
artifacts, and runs the suites on Chromium and mobile Chrome. Browser
install is cached and retried to avoid intermittent download stalls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 10:49:08 +02:00

254 lines
5.7 KiB
JavaScript

// Tasks to run rspack.
import fs from "fs";
import path from "path";
import log from "fancy-log";
import gulp from "gulp";
import rspack from "@rspack/core";
import { RspackDevServer } from "@rspack/dev-server";
import env from "../env.cjs";
import paths from "../paths.cjs";
import {
createAppConfig,
createCastConfig,
createDemoConfig,
createGalleryConfig,
createLandingPageConfig,
createE2eTestAppConfig,
} from "../rspack.cjs";
const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: true }),
createConfigFunc({ ...params, latestBuild: false }),
];
const isWsl =
fs.existsSync("/proc/version") &&
fs
.readFileSync("/proc/version", "utf-8")
.toLocaleLowerCase()
.includes("microsoft");
/**
* @param {{
* compiler: import("@rspack/core").Compiler,
* contentBase: string,
* port: number,
* listenHost?: string,
* open?: boolean,
* logUrlAfterFirstBuild?: boolean,
* }}
*/
const runDevServer = async ({
compiler,
contentBase,
port,
listenHost = undefined,
open = true,
logUrlAfterFirstBuild = false,
proxy = undefined,
}) => {
if (listenHost === undefined) {
// For dev container, we need to listen on all hosts
listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost";
}
const url = `http://localhost:${port}`;
let loggedUrl = false;
if (logUrlAfterFirstBuild) {
compiler.hooks.done.tap("log-dev-server-url", () => {
if (loggedUrl) {
return;
}
loggedUrl = true;
setTimeout(() => {
log("[rspack-dev-server]", `Project is running at ${url}`);
}, 0);
});
}
const server = new RspackDevServer(
{
hot: false,
open,
host: listenHost,
port,
static: {
directory: contentBase,
watch: true,
},
client: {
overlay: {
runtimeErrors: (error) =>
!error?.message?.includes("ResizeObserver loop"),
},
},
proxy,
},
compiler
);
await server.start();
// Server listening
if (!logUrlAfterFirstBuild) {
log("[rspack-dev-server]", `Project is running at ${url}`);
}
};
const doneHandler = (done) => (err, stats) => {
if (err) {
log.error(err.stack || err);
if (err.details) {
log.error(err.details);
}
return;
}
if (stats.hasErrors() || stats.hasWarnings()) {
console.log(stats.toString("minimal"));
}
log(`Build done @ ${new Date().toLocaleTimeString()}`);
if (done) {
done();
}
};
const prodBuild = (conf) =>
new Promise((resolve) => {
rspack(
conf,
// Resolve promise when done. Because we pass a callback, rspack closes itself
doneHandler(resolve)
);
});
gulp.task("rspack-watch-app", () => {
// This command will run forever because we don't close compiler
rspack(
process.env.ES5
? bothBuilds(createAppConfig, { isProdBuild: false })
: createAppConfig({ isProdBuild: false, latestBuild: true })
).watch({ poll: isWsl }, doneHandler());
gulp.watch(
path.join(paths.translations_src, "en.json"),
gulp.series("build-translations", "copy-translations-app")
);
});
gulp.task("rspack-prod-app", () =>
prodBuild(
bothBuilds(createAppConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
isTestBuild: env.isTestBuild(),
})
)
);
gulp.task("rspack-dev-server-demo", () =>
runDevServer({
compiler: rspack(
createDemoConfig({ isProdBuild: false, latestBuild: true })
),
contentBase: paths.demo_output_root,
port: 8090,
})
);
gulp.task("rspack-prod-demo", () =>
prodBuild(
bothBuilds(createDemoConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
})
)
);
gulp.task("rspack-dev-server-cast", () =>
runDevServer({
compiler: rspack(
createCastConfig({ isProdBuild: false, latestBuild: true })
),
contentBase: paths.cast_output_root,
port: 8080,
// Accessible from the network, because that's how Cast hits it.
listenHost: "0.0.0.0",
})
);
gulp.task("rspack-prod-cast", () =>
prodBuild(
bothBuilds(createCastConfig, {
isProdBuild: true,
})
)
);
gulp.task("rspack-dev-server-gallery", () =>
runDevServer({
compiler: rspack(
createGalleryConfig({ isProdBuild: false, latestBuild: true })
),
contentBase: paths.gallery_output_root,
port: 8100,
listenHost: "0.0.0.0",
open: false,
logUrlAfterFirstBuild: true,
})
);
gulp.task("rspack-prod-gallery", () =>
prodBuild(
createGalleryConfig({
isProdBuild: true,
latestBuild: true,
})
)
);
gulp.task("rspack-watch-landing-page", () => {
// This command will run forever because we don't close compiler
rspack(
process.env.ES5
? bothBuilds(createLandingPageConfig, { isProdBuild: false })
: createLandingPageConfig({ isProdBuild: false, latestBuild: true })
).watch({ poll: isWsl }, doneHandler());
gulp.watch(
path.join(paths.translations_src, "en.json"),
gulp.series(
"build-landing-page-translations",
"copy-translations-landing-page"
)
);
});
gulp.task("rspack-prod-landing-page", () =>
prodBuild(
bothBuilds(createLandingPageConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
isTestBuild: env.isTestBuild(),
})
)
);
gulp.task("rspack-dev-server-e2e-test-app", () =>
runDevServer({
compiler: rspack(
createE2eTestAppConfig({ isProdBuild: false, latestBuild: true })
),
contentBase: paths.e2eTestApp_output_root,
port: 8095,
})
);
gulp.task("rspack-prod-e2e-test-app", () =>
prodBuild(
bothBuilds(createE2eTestAppConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
})
)
);