mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-15 12:40:12 +00:00
test: verify P2P progress and native cleanup in Obsidian
Check diagnostic disclosure, responsive peer controls, and current-connection document counters after normal bidirectional synchronisation. Verify that the native API cleanup callback runs once through normal and failed unload. Retain the existing Fetch, recovery, data-preservation, and reload checks, and describe the native scenarios in the E2E guide.
This commit is contained in:
1 parent
b64568aff4
commit
a5d3769cc1
4 files changed
+156
-3
No files matched your search
@@ -4,6 +4,7 @@ import { readFile } from "node:fs/promises";
|
||||
import { connect } from "node:net";
|
||||
import { join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import type { Locator, Page } from "playwright";
|
||||
import { evalObsidianJson } from "../runner/cli.ts";
|
||||
import { discoverObsidianCli, requireObsidianBinary } from "../runner/environment.ts";
|
||||
import { assertEqual, waitForLocalDatabaseEntry } from "../runner/liveSyncWorkflow.ts";
|
||||
@@ -602,6 +603,14 @@ async function waitForDetectedPeer(port: number): Promise<void> {
|
||||
|
||||
async function capturePeerActionsMenu(port: number): Promise<string> {
|
||||
await withObsidianPage(port, async (page) => {
|
||||
const detectedPeers = page.getByRole("heading", { name: "Detected Peers" }).last();
|
||||
await detectedPeers.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
const pane = detectedPeers.locator(
|
||||
"xpath=ancestor::*[contains(concat(' ', normalize-space(@class), ' '), ' workspace-leaf-content ')][1]"
|
||||
);
|
||||
await assertP2PDiagnosticSummary(pane);
|
||||
await assertPeerActionButtonsFit(pane);
|
||||
|
||||
const moreActions = page.getByRole("button", { name: /^More actions for /u }).first();
|
||||
await moreActions.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
await moreActions.click({ timeout: uiTimeoutMs });
|
||||
@@ -641,6 +650,117 @@ async function capturePeerActionsMenu(port: number): Promise<string> {
|
||||
return screenshot;
|
||||
}
|
||||
|
||||
async function assertP2PDiagnosticSummary(pane: Locator): Promise<void> {
|
||||
const stats = pane.getByRole("heading", { name: "Stats", exact: true });
|
||||
await stats.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
const diagnosticDetails = pane.locator("details.diag-details");
|
||||
await diagnosticDetails.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
if (await diagnosticDetails.evaluate((element) => (element as HTMLDetailsElement).open)) {
|
||||
throw new Error("The P2P diagnostic summary was open before the user expanded it.");
|
||||
}
|
||||
const diagnosticSummary = diagnosticDetails.locator("summary");
|
||||
await diagnosticSummary.click({ timeout: uiTimeoutMs });
|
||||
const diagnosticRows = diagnosticDetails.locator(".diag-summary > div");
|
||||
await diagnosticRows.first().waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
const rowText = await diagnosticRows.allInnerTexts();
|
||||
for (const label of ["Relay:", "Peer:", "Sync:"]) {
|
||||
if (!rowText.some((text) => text.trim().startsWith(label))) {
|
||||
throw new Error(`The expanded P2P diagnostics did not show the ${label} observation.`);
|
||||
}
|
||||
}
|
||||
const openLog = diagnosticDetails.getByRole("button", { name: "Open log", exact: true });
|
||||
await openLog.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
if (!(await stats.isVisible())) {
|
||||
throw new Error("The P2P Stats section was hidden while diagnostic observations were expanded.");
|
||||
}
|
||||
await diagnosticSummary.click({ timeout: uiTimeoutMs });
|
||||
await diagnosticRows.first().waitFor({ state: "hidden", timeout: uiTimeoutMs });
|
||||
if (await diagnosticDetails.evaluate((element) => (element as HTMLDetailsElement).open)) {
|
||||
throw new Error("The P2P diagnostic summary did not collapse after it was toggled again.");
|
||||
}
|
||||
if (!(await stats.isVisible())) {
|
||||
throw new Error("The P2P Stats section was hidden after collapsing diagnostic observations.");
|
||||
}
|
||||
}
|
||||
|
||||
async function assertPeerActionButtonsFit(pane: Locator): Promise<void> {
|
||||
const peerItem = pane.locator(".peers-section .peer-item").first();
|
||||
await peerItem.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
await peerItem.scrollIntoViewIfNeeded();
|
||||
const actionButtons = await peerItem.evaluate((element) => {
|
||||
const item = element.getBoundingClientRect();
|
||||
const viewport = {
|
||||
width: document.documentElement.clientWidth,
|
||||
height: document.documentElement.clientHeight,
|
||||
};
|
||||
return {
|
||||
hasHorizontalOverflow: element.scrollWidth > element.clientWidth,
|
||||
buttons: Array.from(element.querySelectorAll("button"))
|
||||
.filter((button) => button.getClientRects().length > 0)
|
||||
.map((button) => {
|
||||
const rect = button.getBoundingClientRect();
|
||||
return {
|
||||
label: button.getAttribute("aria-label") ?? button.textContent?.trim() ?? "",
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
insidePeer:
|
||||
rect.left >= item.left &&
|
||||
rect.top >= item.top &&
|
||||
rect.right <= item.right &&
|
||||
rect.bottom <= item.bottom,
|
||||
insideViewport:
|
||||
rect.left >= 0 &&
|
||||
rect.top >= 0 &&
|
||||
rect.right <= viewport.width &&
|
||||
rect.bottom <= viewport.height,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
if (
|
||||
actionButtons.buttons.length === 0 ||
|
||||
actionButtons.hasHorizontalOverflow ||
|
||||
actionButtons.buttons.some(
|
||||
(button) =>
|
||||
button.width < 44 ||
|
||||
button.height < 44 ||
|
||||
!button.insidePeer ||
|
||||
!button.insideViewport
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`P2P peer action buttons were too small or did not fit their peer item and viewport: ${JSON.stringify(actionButtons)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function assertPeerDocumentProgress(page: Page, directions: readonly ("↑" | "↓")[]): Promise<void> {
|
||||
const detectedPeers = page.getByRole("heading", { name: "Detected Peers" }).last();
|
||||
await detectedPeers.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
const pane = detectedPeers.locator(
|
||||
"xpath=ancestor::*[contains(concat(' ', normalize-space(@class), ' '), ' workspace-leaf-content ')][1]"
|
||||
);
|
||||
const progress = pane.locator(".peers-section .peer-item .peer-progress").first();
|
||||
await progress.waitFor({ state: "visible", timeout: uiTimeoutMs });
|
||||
const deadline = Date.now() + uiTimeoutMs;
|
||||
let message = "";
|
||||
while (Date.now() < deadline) {
|
||||
message = (await progress.innerText()).replace(/\s+/gu, " ").trim();
|
||||
const documentProgress = message.replace(/⏩[↑↓]\s+\d+/gu, "");
|
||||
if (
|
||||
directions.every((direction) =>
|
||||
new RegExp(`${direction}\\s+\\d+(?:\\s|$)`, "u").test(documentProgress)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await page.waitForTimeout(100);
|
||||
}
|
||||
throw new Error(
|
||||
`Completed P2P transfer progress did not show document counters for ${directions.join(" and ")}: ${message}`
|
||||
);
|
||||
}
|
||||
|
||||
async function captureNote(port: number, path: string, text: string, filename: string): Promise<string> {
|
||||
await withObsidianPage(port, async (page) => {
|
||||
await page.evaluate((notePath) => {
|
||||
@@ -740,6 +860,9 @@ async function main(): Promise<void> {
|
||||
console.log("P2P workflow: return replication requested; waiting for the second device's note.");
|
||||
await waitForPathContent(vaultA, noteFromSecond, secondContent);
|
||||
console.log("P2P workflow: return note reached the first device.");
|
||||
await withObsidianPage(portA, async (page) => {
|
||||
await assertPeerDocumentProgress(page, ["↑", "↓"]);
|
||||
});
|
||||
} finally {
|
||||
returnJourneyFinished = true;
|
||||
await returnJourneyAcceptor;
|
||||
|
||||
Reference in new issue
Block a user