mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-06-26 16:13:57 +00:00
(test): the E2E test on the real-Obsidian
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { discoverObsidianCli, requireObsidianBinary } from "../runner/environment.ts";
|
||||
import { launchObsidian } from "../runner/launch.ts";
|
||||
import { runObsidianCli } from "../runner/cli.ts";
|
||||
import { createTemporaryVault } from "../runner/vault.ts";
|
||||
import { installBuiltPlugin } from "../runner/pluginInstaller.ts";
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const binary = requireObsidianBinary();
|
||||
const cli = discoverObsidianCli();
|
||||
if (!cli.binary) {
|
||||
throw new Error(`Could not find obsidian-cli. Checked paths: ${cli.checked.join(", ")}`);
|
||||
}
|
||||
const vault = await createTemporaryVault();
|
||||
let app;
|
||||
try {
|
||||
await installBuiltPlugin(vault.path);
|
||||
app = await launchObsidian({
|
||||
binary,
|
||||
vaultPath: vault.path,
|
||||
homePath: vault.homePath,
|
||||
xdgConfigPath: vault.xdgConfigPath,
|
||||
userDataPath: vault.userDataPath,
|
||||
});
|
||||
const cliEnv = {
|
||||
...process.env,
|
||||
HOME: vault.homePath,
|
||||
XDG_CONFIG_HOME: vault.xdgConfigPath,
|
||||
};
|
||||
await runObsidianCli(cli.binary, [`obsidian://open?path=${encodeURIComponent(vault.path)}`], cliEnv);
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
if (process.env.E2E_OBSIDIAN_RELOAD_PLUGIN === "true") {
|
||||
await runObsidianCli(cli.binary, ["eval", "code=(async()=>app.plugins.setEnable(true))()"], cliEnv);
|
||||
await runObsidianCli(cli.binary, ["plugin:reload", "id=obsidian-livesync"], cliEnv);
|
||||
}
|
||||
const cliArgs = process.argv.slice(2);
|
||||
const result = await runObsidianCli(cli.binary, cliArgs.length > 0 ? cliArgs : ["--help"], cliEnv);
|
||||
console.log(result.stdout);
|
||||
console.error(result.stderr);
|
||||
process.exitCode = result.code ?? 1;
|
||||
} finally {
|
||||
if (app) {
|
||||
await app.stop();
|
||||
}
|
||||
await vault.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
console.error(error instanceof Error ? error.stack : error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { discoverObsidianBinary } from "../runner/environment.ts";
|
||||
|
||||
const result = discoverObsidianBinary();
|
||||
if (result.binary) {
|
||||
console.log(`Obsidian executable: ${result.binary}`);
|
||||
console.log(`Source: ${result.source}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.error("Obsidian executable was not found.");
|
||||
console.error("Set OBSIDIAN_BINARY to the installed Obsidian executable path.");
|
||||
console.error(`Checked paths: ${result.checked.length > 0 ? result.checked.join(", ") : "(none)"}`);
|
||||
process.exit(1);
|
||||
@@ -0,0 +1,121 @@
|
||||
import { createWriteStream, existsSync } from "node:fs";
|
||||
import { chmod, mkdir } from "node:fs/promises";
|
||||
import { get } from "node:https";
|
||||
import { arch } from "node:process";
|
||||
import { basename, join, resolve } from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
const defaultVersion = "1.12.7";
|
||||
|
||||
function appImageArch(): string {
|
||||
const requestedArch = process.env.E2E_OBSIDIAN_APPIMAGE_ARCH?.trim();
|
||||
if (requestedArch) {
|
||||
return requestedArch;
|
||||
}
|
||||
if (arch === "arm64") {
|
||||
return "arm64";
|
||||
}
|
||||
if (arch === "x64") {
|
||||
return "x86_64";
|
||||
}
|
||||
throw new Error(`Unsupported architecture for Obsidian AppImage: ${arch}`);
|
||||
}
|
||||
|
||||
function appImageUrl(version: string, imageArch: string): string {
|
||||
return `https://github.com/obsidianmd/obsidian-releases/releases/download/v${version}/Obsidian-${version}-${imageArch}.AppImage`;
|
||||
}
|
||||
|
||||
function download(url: string, destination: string, redirectsLeft = 5): Promise<void> {
|
||||
return new Promise((resolveDownload, reject) => {
|
||||
const request = get(url, (response) => {
|
||||
const statusCode = response.statusCode ?? 0;
|
||||
const location = response.headers.location;
|
||||
if (statusCode >= 300 && statusCode < 400 && location) {
|
||||
response.resume();
|
||||
if (redirectsLeft <= 0) {
|
||||
reject(new Error(`Too many redirects while downloading ${url}`));
|
||||
return;
|
||||
}
|
||||
download(new URL(location, url).toString(), destination, redirectsLeft - 1)
|
||||
.then(resolveDownload)
|
||||
.catch(reject);
|
||||
return;
|
||||
}
|
||||
if (statusCode !== 200) {
|
||||
response.resume();
|
||||
reject(new Error(`Failed to download ${url}: HTTP ${statusCode}`));
|
||||
return;
|
||||
}
|
||||
|
||||
const file = createWriteStream(destination, { mode: 0o755 });
|
||||
response.pipe(file);
|
||||
file.on("finish", () => {
|
||||
file.close((error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolveDownload();
|
||||
}
|
||||
});
|
||||
});
|
||||
file.on("error", reject);
|
||||
});
|
||||
request.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
function extractAppImage(appImagePath: string, cwd: string): Promise<void> {
|
||||
return new Promise((resolveExtract, reject) => {
|
||||
const child = spawn(appImagePath, ["--appimage-extract"], {
|
||||
cwd,
|
||||
stdio: "inherit",
|
||||
});
|
||||
child.on("error", reject);
|
||||
child.on("exit", (code, signal) => {
|
||||
if (code === 0) {
|
||||
resolveExtract();
|
||||
return;
|
||||
}
|
||||
reject(new Error(`AppImage extraction failed. code=${code}, signal=${signal}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const version = process.env.E2E_OBSIDIAN_VERSION?.trim() || defaultVersion;
|
||||
const imageArch = appImageArch();
|
||||
const targetDir = resolve(process.env.E2E_OBSIDIAN_DOWNLOAD_DIR?.trim() || "_testdata/obsidian");
|
||||
const url = process.env.E2E_OBSIDIAN_APPIMAGE_URL?.trim() || appImageUrl(version, imageArch);
|
||||
const appImagePath = join(targetDir, basename(new URL(url).pathname));
|
||||
const extractedBinary = join(targetDir, "squashfs-root", "obsidian");
|
||||
const forceDownload = process.env.E2E_OBSIDIAN_FORCE_DOWNLOAD === "true";
|
||||
const skipExtract = process.env.E2E_OBSIDIAN_SKIP_EXTRACT === "true";
|
||||
|
||||
await mkdir(targetDir, { recursive: true });
|
||||
|
||||
if (!existsSync(appImagePath) || forceDownload) {
|
||||
console.log(`Downloading Obsidian AppImage: ${url}`);
|
||||
console.log(`Destination: ${appImagePath}`);
|
||||
await download(url, appImagePath);
|
||||
await chmod(appImagePath, 0o755);
|
||||
} else {
|
||||
console.log(`Using existing Obsidian AppImage: ${appImagePath}`);
|
||||
}
|
||||
|
||||
if (!skipExtract) {
|
||||
if (existsSync(extractedBinary)) {
|
||||
console.log(`Using existing extracted Obsidian binary: ${extractedBinary}`);
|
||||
} else {
|
||||
console.log(`Extracting Obsidian AppImage in ${targetDir}`);
|
||||
await extractAppImage(appImagePath, targetDir);
|
||||
console.log(`Extracted Obsidian binary: ${extractedBinary}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Set OBSIDIAN_BINARY=${extractedBinary} to use the extracted binary explicitly.`);
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
console.error(error instanceof Error ? error.stack : error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
import { discoverObsidianCli, requireObsidianBinary } from "../runner/environment.ts";
|
||||
import { launchObsidian } from "../runner/launch.ts";
|
||||
import { installBuiltPlugin } from "../runner/pluginInstaller.ts";
|
||||
import { waitForPluginReady } from "../runner/readiness.ts";
|
||||
import { createTemporaryVault } from "../runner/vault.ts";
|
||||
import { openVaultWithObsidianCli, runObsidianCli } from "../runner/cli.ts";
|
||||
|
||||
async function waitForPluginCatalogue(cliBinary: string, env: NodeJS.ProcessEnv): Promise<void> {
|
||||
const deadline = Date.now() + Number(process.env.E2E_OBSIDIAN_CLI_READY_TIMEOUT_MS ?? 15000);
|
||||
let lastOutput = "";
|
||||
while (Date.now() < deadline) {
|
||||
const result = await runObsidianCli(cliBinary, ["plugins", "filter=community"], env);
|
||||
lastOutput = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
||||
if (result.stdout.includes("obsidian-livesync")) {
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
}
|
||||
throw new Error(`Timed out waiting for Obsidian plug-in catalogue through CLI.\n${lastOutput}`);
|
||||
}
|
||||
|
||||
async function enableCommunityPlugins(cliBinary: string, env: NodeJS.ProcessEnv): Promise<void> {
|
||||
const result = await runObsidianCli(cliBinary, ["eval", "code=(async()=>app.plugins.setEnable(true))()"], env);
|
||||
if (result.code !== 0 || result.stdout.includes("Error:")) {
|
||||
throw new Error(
|
||||
[
|
||||
`Failed to enable Obsidian community plug-ins through CLI. code=${result.code}, signal=${result.signal}`,
|
||||
result.stdout ? `stdout:\n${result.stdout}` : undefined,
|
||||
result.stderr ? `stderr:\n${result.stderr}` : undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const binary = requireObsidianBinary();
|
||||
const cli = discoverObsidianCli();
|
||||
if (!cli.binary) {
|
||||
throw new Error(`Could not find obsidian-cli. Checked paths: ${cli.checked.join(", ")}`);
|
||||
}
|
||||
const vault = await createTemporaryVault();
|
||||
let app;
|
||||
try {
|
||||
const install = await installBuiltPlugin(vault.path);
|
||||
console.log(`Using Obsidian executable: ${binary}`);
|
||||
console.log(`Temporary vault: ${vault.path}`);
|
||||
console.log(`Installed plug-in artifacts: ${install.copied.join(", ")}`);
|
||||
|
||||
app = await launchObsidian({
|
||||
binary,
|
||||
vaultPath: vault.path,
|
||||
homePath: vault.homePath,
|
||||
xdgConfigPath: vault.xdgConfigPath,
|
||||
userDataPath: vault.userDataPath,
|
||||
startupGraceMs: Number(process.env.E2E_OBSIDIAN_STARTUP_GRACE_MS ?? 1000),
|
||||
});
|
||||
const cliEnv = {
|
||||
...process.env,
|
||||
HOME: vault.homePath,
|
||||
XDG_CONFIG_HOME: vault.xdgConfigPath,
|
||||
};
|
||||
await openVaultWithObsidianCli(cli.binary, vault.path, cliEnv);
|
||||
await waitForPluginCatalogue(cli.binary, cliEnv);
|
||||
await enableCommunityPlugins(cli.binary, cliEnv);
|
||||
const reload = await runObsidianCli(cli.binary, ["plugin:reload", "id=obsidian-livesync"], cliEnv);
|
||||
if (reload.code !== 0 || !reload.stdout.includes("Reloaded: obsidian-livesync")) {
|
||||
throw new Error(
|
||||
[
|
||||
`Failed to reload Self-hosted LiveSync through Obsidian CLI. code=${reload.code}, signal=${reload.signal}`,
|
||||
reload.stdout ? `stdout:\n${reload.stdout}` : undefined,
|
||||
reload.stderr ? `stderr:\n${reload.stderr}` : undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
);
|
||||
}
|
||||
const readiness = await waitForPluginReady(cli.binary, cliEnv);
|
||||
console.log(
|
||||
`Obsidian plug-in ready: ${readiness.pluginId}@${readiness.pluginVersion} in ${readiness.vaultName}`
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, Number(process.env.E2E_OBSIDIAN_SMOKE_TIMEOUT_MS ?? 1000)));
|
||||
console.log("Obsidian stayed alive after the plug-in readiness check.");
|
||||
} finally {
|
||||
if (app) {
|
||||
await app.stop();
|
||||
}
|
||||
await vault.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
console.error(error instanceof Error ? error.stack : error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user