mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-11-02 19:24:03 +01:00
test: Linux e2e on Xvfb
This commit is contained in:
1 parent
cbaaf312c4
commit
b3efaa42b7
3 files changed
+88
-20
No files matched your search
+8
-5
@@ -44,11 +44,14 @@ harness in `e2e/app`, then installs the `tauri-wd` CLI into the ignored `e2e/.dr
|
||||
version pinned by `e2e/app/Cargo.toml` is not already there. The harness enables Donut's `e2e`
|
||||
feature and injects the WebDriver plugin so the production crate never depends on it. Both the
|
||||
plugin and the CLI come from the same pinned crates.io release, so they cannot drift apart. Bump
|
||||
the pin in `e2e/app/Cargo.toml` to move to a newer driver. Every suite runs the Donut window
|
||||
headless (on macOS the window is transparent, click-through and never focused; elsewhere it is
|
||||
hidden), so a run never pops a window or steals focus. Set `DONUT_E2E_HEADED=1` to watch the
|
||||
window while debugging a failure; Wayfern browsers launched by a test are separate processes and
|
||||
show their own windows unless the test asks for a headless launch.
|
||||
the pin in `e2e/app/Cargo.toml` to move to a newer driver. Every suite keeps the Donut window off
|
||||
your screen, so a run never pops a window or steals focus: on macOS the window is transparent,
|
||||
click-through and never focused, and on Windows it is hidden. On Linux the runner starts a private
|
||||
Xvfb screen (install the `xvfb` package) and shows the window and every Wayfern window there,
|
||||
because GTK gives a hidden window no size and its page would have a 0x0 viewport. Set
|
||||
`DONUT_E2E_HEADED=1` to watch the window on your display while debugging a failure; Wayfern
|
||||
browsers launched by a test are separate processes and show their own windows unless the test
|
||||
asks for a headless launch.
|
||||
Add `--no-build` to
|
||||
`node e2e/run.mjs --suite=<name>` only when all four outputs are current.
|
||||
`DONUT_E2E_KEEP_ARTIFACTS=1` retains successful local runs; failed runs are always retained and
|
||||
|
||||
+5
-4
@@ -285,10 +285,11 @@ export class AppSession {
|
||||
env,
|
||||
cwd: this.cwd,
|
||||
startupTimeout: 120_000,
|
||||
// Set by run.mjs for every suite. The driver keeps the Donut window off
|
||||
// the user's screen (on macOS transparent, click-through and never key,
|
||||
// with the app as an accessory; hidden elsewhere), so a suite never
|
||||
// pops a window or steals focus.
|
||||
// Set by run.mjs on macOS and Windows. The driver keeps the Donut window
|
||||
// off the user's screen (on macOS transparent, click-through and never
|
||||
// key, with the app as an accessory; hidden on Windows), so a suite
|
||||
// never pops a window or steals focus. Linux runs show the window on a
|
||||
// private display instead, because a hidden GTK window has no size.
|
||||
headless: process.env.DONUT_E2E_HEADLESS === "1",
|
||||
});
|
||||
await this.session.setTimeouts();
|
||||
|
||||
+75
-11
@@ -217,6 +217,53 @@ async function stopProcess(record) {
|
||||
record.stream.end();
|
||||
}
|
||||
|
||||
// GTK never gives a hidden window a size, so a hidden Donut window on Linux
|
||||
// lays its page out in a 0x0 viewport: no element has a box, nothing can be
|
||||
// clicked, and every check of what a person sees fails. Linux runs show the
|
||||
// window on a private Xvfb screen instead, where nobody sees it and the page
|
||||
// has its real size. Returns the display name.
|
||||
async function startPrivateDisplay(runRoot, records) {
|
||||
const logPath = path.join(runRoot, "logs", "xvfb.log");
|
||||
const stream = createWriteStream(logPath, { flags: "a" });
|
||||
// -displayfd takes the first free display and writes its number to fd 3.
|
||||
const child = spawn(
|
||||
"Xvfb",
|
||||
["-displayfd", "3", "-screen", "0", "1920x1080x24", "-nolisten", "tcp"],
|
||||
{ detached: true, stdio: ["ignore", "pipe", "pipe", "pipe"] },
|
||||
);
|
||||
child.stdout.pipe(stream, { end: false });
|
||||
child.stderr.pipe(stream, { end: false });
|
||||
records.push({ name: "xvfb", process: child, stream, logPath });
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(
|
||||
() => reject(new Error(`Xvfb named no display; see ${logPath}`)),
|
||||
15_000,
|
||||
);
|
||||
child.once("error", (error) => {
|
||||
clearTimeout(timer);
|
||||
reject(
|
||||
error.code === "ENOENT"
|
||||
? new Error(
|
||||
"Linux runs need Xvfb (package xvfb); DONUT_E2E_HEADED=1 uses the current display instead",
|
||||
)
|
||||
: error,
|
||||
);
|
||||
});
|
||||
child.once("exit", (code, signal) => {
|
||||
clearTimeout(timer);
|
||||
reject(new Error(`Xvfb exited with ${code ?? signal}; see ${logPath}`));
|
||||
});
|
||||
let written = "";
|
||||
child.stdio[3].on("data", (chunk) => {
|
||||
written += chunk;
|
||||
if (written.includes("\n")) {
|
||||
clearTimeout(timer);
|
||||
resolve(`:${written.trim()}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function unquoteEnvValue(value) {
|
||||
const trimmed = value.trim();
|
||||
const quote = trimmed[0];
|
||||
@@ -969,6 +1016,20 @@ async function main() {
|
||||
}
|
||||
installSidecars();
|
||||
|
||||
const headed = process.env.DONUT_E2E_HEADED === "1";
|
||||
const privateDisplay =
|
||||
process.platform === "linux" && !headed
|
||||
? await startPrivateDisplay(runRoot, records)
|
||||
: null;
|
||||
// The driver passes its environment to the app, and the app to Wayfern.
|
||||
const appEnv = { ...process.env };
|
||||
if (privateDisplay) {
|
||||
appEnv.DISPLAY = privateDisplay;
|
||||
appEnv.GDK_BACKEND = "x11";
|
||||
delete appEnv.WAYLAND_DISPLAY;
|
||||
log(`Private display ${privateDisplay}`);
|
||||
}
|
||||
|
||||
const driverPort = await freePort();
|
||||
const driver = startProcess(
|
||||
"tauri-wd",
|
||||
@@ -989,7 +1050,7 @@ async function main() {
|
||||
],
|
||||
{
|
||||
cwd: projectRoot,
|
||||
env: process.env,
|
||||
env: appEnv,
|
||||
runRoot,
|
||||
verbose: options.verbose,
|
||||
},
|
||||
@@ -1053,7 +1114,7 @@ async function main() {
|
||||
const child = spawn(process.execPath, testArgs, {
|
||||
cwd: projectRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
...appEnv,
|
||||
DONUT_E2E_RUN_ROOT: runRoot,
|
||||
DONUT_E2E_PROJECT_ROOT: projectRoot,
|
||||
DONUT_E2E_APP: appBinary,
|
||||
@@ -1061,15 +1122,18 @@ async function main() {
|
||||
DONUT_E2E_FIXTURE_URL: `http://127.0.0.1:${fixture.port}`,
|
||||
DONUT_E2E_GEOIP_FIXTURE_READY: geoIpFixture ? "1" : "0",
|
||||
DONUT_E2E_GEOIP_ASN_FIXTURE_READY: geoIpAsnFixture ? "1" : "0",
|
||||
// Every suite runs the Donut window headless so a local run never
|
||||
// pops a window or steals focus: the app builds it hidden and the
|
||||
// tauri-wd plugin keeps it off screen (on macOS transparent,
|
||||
// click-through and never key, with the app as an accessory; hidden
|
||||
// elsewhere). AppSession forwards this as the headless capability;
|
||||
// only the driver's TAURI_WEBDRIVER_HEADLESS reaches the app.
|
||||
// DONUT_E2E_HEADED=1 shows the window again when a failure needs
|
||||
// watching. Wayfern itself is a separate process and unaffected.
|
||||
DONUT_E2E_HEADLESS: process.env.DONUT_E2E_HEADED === "1" ? "0" : "1",
|
||||
// Every suite keeps the Donut window off the user's screen so a local
|
||||
// run never pops a window or steals focus. On macOS and Windows it
|
||||
// runs headless: the app builds the window hidden and the tauri-wd
|
||||
// plugin keeps it off screen (on macOS transparent, click-through and
|
||||
// never key, with the app as an accessory; hidden on Windows).
|
||||
// AppSession forwards this as the headless capability; only the
|
||||
// driver's TAURI_WEBDRIVER_HEADLESS reaches the app. On Linux the
|
||||
// window is shown on the private display instead (see
|
||||
// startPrivateDisplay), and Wayfern opens there too.
|
||||
// DONUT_E2E_HEADED=1 shows the window on the current display when a
|
||||
// failure needs watching.
|
||||
DONUT_E2E_HEADLESS: headed || privateDisplay ? "0" : "1",
|
||||
WAYFERN_TEST_TOKEN: token,
|
||||
RESIDENTIAL_PROXY_URL_ONE_SOCKS:
|
||||
localValues.RESIDENTIAL_PROXY_URL_ONE_SOCKS ?? "",
|
||||
|
||||
Reference in new issue
Block a user