mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-18 15:22:31 +00:00
Harden real-runtime workflow validation
This commit is contained in:
@@ -28,14 +28,16 @@ export async function startHeldOneShotReplication(cliBinary: string, env: NodeJS
|
||||
"const core=app.plugins.plugins['obsidian-livesync'].core;",
|
||||
"const replicator=core.services.replicator.getActiveReplicator();",
|
||||
"if(!replicator) throw new Error('No active replicator is available.');",
|
||||
"const original=replicator.openReplication;",
|
||||
"const methodName=typeof replicator.openOneShotReplicationWithOutcome==='function'?'openOneShotReplicationWithOutcome':'openReplication';",
|
||||
"const original=replicator[methodName];",
|
||||
"if(typeof original!=='function') throw new Error('The active replicator has no one-shot entry point.');",
|
||||
"let releaseGate;",
|
||||
"const gate=new Promise((resolve)=>{releaseGate=resolve;});",
|
||||
`const state={kind:${JSON.stringify(REMOTE_ACTIVITY_GATE_KIND.oneShot)},entered:false,done:false,released:false,error:undefined,result:undefined,promise:undefined,release:undefined,restore:undefined};`,
|
||||
"state.release=()=>{if(!state.released){state.released=true;releaseGate();}};",
|
||||
"state.restore=()=>{replicator.openReplication=original;};",
|
||||
"state.restore=()=>{replicator[methodName]=original;};",
|
||||
"host[stateKey]=state;",
|
||||
"replicator.openReplication=async function(...args){",
|
||||
"replicator[methodName]=async function(...args){",
|
||||
"state.entered=true;",
|
||||
"await gate;",
|
||||
"return await original.apply(this,args);",
|
||||
|
||||
@@ -38,7 +38,12 @@ export function modalByTitle(page: Page, title: string): Locator {
|
||||
}
|
||||
|
||||
export async function captureGuideDialogue(port: number, filename: string, title: string): Promise<string> {
|
||||
return await captureObsidianElement(port, filename, (page) => modalByTitle(page, title).locator(".modal").first());
|
||||
return await captureObsidianElement(
|
||||
port,
|
||||
filename,
|
||||
(page) => modalByTitle(page, title).locator(".modal").first(),
|
||||
uiTimeoutMs
|
||||
);
|
||||
}
|
||||
|
||||
export async function assertVerticalActionLayout(port: number, title: string): Promise<void> {
|
||||
@@ -182,16 +187,16 @@ export async function captureAndStartInitialisation(
|
||||
? "Setup Complete: Preparing This P2P Device"
|
||||
: p2pAdditionalDevice
|
||||
? "Setup Complete: Preparing to Fetch from Another Device"
|
||||
: mode === "new"
|
||||
? "Setup Complete: Preparing to Initialise Server"
|
||||
: "Setup Complete: Preparing to Fetch Synchronisation Data";
|
||||
: mode === "new"
|
||||
? "Setup Complete: Preparing to Initialise Server"
|
||||
: "Setup Complete: Preparing to Fetch Synchronisation Data";
|
||||
const button = p2pFirstDevice
|
||||
? "Restart and Prepare This Device"
|
||||
: p2pAdditionalDevice
|
||||
? "Restart and Select Source Device"
|
||||
: mode === "new"
|
||||
? "Restart and Initialise Server"
|
||||
: "Restart and Fetch Data";
|
||||
: mode === "new"
|
||||
? "Restart and Initialise Server"
|
||||
: "Restart and Fetch Data";
|
||||
if (p2pAdditionalDevice) {
|
||||
await withObsidianPage(port, async (page) => {
|
||||
const modal = modalByTitle(page, title);
|
||||
|
||||
@@ -115,7 +115,6 @@ export async function openLiveSyncSettings(page: Page, timeoutMs = 10_000): Prom
|
||||
const setting = host.app?.setting;
|
||||
if (setting === undefined) throw new Error("Obsidian settings are unavailable");
|
||||
setting.open();
|
||||
setting.openTabById("obsidian-livesync");
|
||||
});
|
||||
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
@@ -137,6 +136,16 @@ export async function openLiveSyncSettings(page: Page, timeoutMs = 10_000): Prom
|
||||
}
|
||||
if (settingsPage === undefined) throw new Error("Obsidian did not open its settings interface");
|
||||
|
||||
// Obsidian may discard a tab selection made before the settings modal has
|
||||
// finished opening. Select the plug-in only after a settings renderer is
|
||||
// visible so slower real-runtime sessions cannot remain on the About tab.
|
||||
await hostPage.evaluate(() => {
|
||||
const host = globalThis as ObsidianSettingsHost;
|
||||
const setting = host.app?.setting;
|
||||
if (setting === undefined) throw new Error("Obsidian settings are unavailable");
|
||||
setting.openTabById("obsidian-livesync");
|
||||
});
|
||||
|
||||
const dialogue = settingsPage.locator(".modal.mod-settings:visible").last();
|
||||
const imperativeRoot = dialogue.locator(".sls-setting:visible").last();
|
||||
const firstDeclarativeEntry = declarativePageEntry(dialogue, "Change Log");
|
||||
@@ -270,7 +279,8 @@ export async function allowPendingObsidianTestVaultOpenAction(
|
||||
export async function captureObsidianElement(
|
||||
port: number,
|
||||
filename: string,
|
||||
resolveElement: (page: Page) => Locator | Promise<Locator>
|
||||
resolveElement: (page: Page) => Locator | Promise<Locator>,
|
||||
timeoutMs = 10_000
|
||||
): Promise<string> {
|
||||
const outputDirectory = process.env.E2E_OBSIDIAN_DIAGNOSTICS_DIR ?? "/tmp/obsidian-livesync-e2e";
|
||||
const screenshotPath = join(outputDirectory, filename);
|
||||
@@ -279,7 +289,7 @@ export async function captureObsidianElement(
|
||||
await withObsidianPage(port, async (page) => {
|
||||
try {
|
||||
const element = await resolveElement(page);
|
||||
await element.waitFor({ state: "visible", timeout: 10000 });
|
||||
await element.waitFor({ state: "visible", timeout: timeoutMs });
|
||||
await element.screenshot({
|
||||
path: screenshotPath,
|
||||
animations: "disabled",
|
||||
|
||||
Reference in New Issue
Block a user