TidGi-Desktop/features/stepDefinitions/cleanup.ts
linonetwo a0e7a6ec57 Add mobile HTTP git sync tests & merge utilities
Introduce end-to-end tests and server-side support for mobile-style Smart HTTP git sync. Adds a new mobileSyncConflict.feature and extended sync.step definitions to simulate HTTP clone/sync cycles, pushes, and assertions (including file content checks and HTTP readiness/backoff). Introduces src/services/gitServer/mergeUtilities.ts to resolve .tid conflicts (mobile metadata wins, body merged) and common git helpers, and wires those into the git server (use runGitCollectStdout, DESKTOP_GIT_IDENTITY, and mergeAfterPush endpoint). Misc: update workspace restart flow and settings handling, tweak test helpers (skip screenshot capture for file steps), adjust slugify rules, minor UI/formatting change, and bump git-sync-js dependency (with lockfile update).
2026-02-24 16:50:08 +08:00

125 lines
4.6 KiB
TypeScript

import { After, Before } from '@cucumber/cucumber';
import fs from 'fs-extra';
import path from 'path';
import { makeSlugPath } from '../supports/paths';
import { clearAISettings } from './agent';
import { ApplicationWorld } from './application';
import { clearTidgiMiniWindowSettings } from './tidgiMiniWindow';
import { clearHibernationTestData, clearSubWikiRoutingTestData } from './wiki';
Before(async function(this: ApplicationWorld, { pickle }) {
// Initialize scenario-specific paths
this.scenarioName = pickle.name;
this.scenarioSlug = makeSlugPath(pickle.name, 60);
const scenarioRoot = path.resolve(process.cwd(), 'test-artifacts', this.scenarioSlug);
const logsDirectory = path.resolve(scenarioRoot, 'userData-test', 'logs');
const screenshotsDirectory = path.resolve(logsDirectory, 'screenshots');
const wikiTestRoot = path.resolve(scenarioRoot, 'wiki-test');
// Create necessary directories for this scenario
await fs.ensureDir(logsDirectory);
await fs.ensureDir(screenshotsDirectory);
await fs.ensureDir(wikiTestRoot); // Ensure wiki-test root exists for default wiki creation
if (pickle.tags.some((tag) => tag.name === '@ai-setting')) {
await clearAISettings(scenarioRoot);
}
if (pickle.tags.some((tag) => tag.name === '@tidgi-mini-window')) {
await clearTidgiMiniWindowSettings(scenarioRoot);
}
});
After(async function(this: ApplicationWorld, { pickle }) {
// IMPORTANT: Close app FIRST before cleaning up files
// This releases file locks so wiki folders can be deleted
if (this.app) {
try {
// Close all windows including tidgi mini window before closing the app, otherwise it might hang, and refused to exit until ctrl+C
const allWindows = this.app.windows();
// Try to close windows gracefully with short timeout, then force close
await Promise.allSettled(
allWindows.map(async (window) => {
if (window.isClosed()) return;
try {
// Very short timeout for window close - we'll force close anyway
await Promise.race([
window.close(),
new Promise((_, reject) =>
setTimeout(() => {
reject(new Error('Window close timeout'));
}, 1000)
),
]);
} catch {
// Window close failed or timed out, ignore and continue
// Force close will happen at app level
}
}),
);
// Try to close app gracefully with short timeout
try {
await Promise.race([
this.app.close(),
new Promise((_, reject) =>
setTimeout(() => {
reject(new Error('App close timeout'));
}, 1000)
),
]);
} catch {
// App close failed or timed out, force close immediately
}
} catch {
// Any error in the try block, continue to force close
} finally {
// ALWAYS force close, regardless of success/failure above
// This ensures resources are freed even if graceful close hangs
try {
if (this.app) {
// Force close browser context - this kills all processes
await Promise.race([
this.app.context().close({ reason: 'Force cleanup after test' }),
new Promise((resolve) => setTimeout(resolve, 500)), // 500ms max for force close
]);
}
} catch {
// Even force close can fail, but we don't care - move on
}
// Clear references immediately
this.app = undefined;
this.mainWindow = undefined;
this.currentWindow = undefined;
}
}
const scenarioRoot = path.resolve(process.cwd(), 'test-artifacts', this.scenarioSlug);
// Clean up settings and test data AFTER app is closed
if (pickle.tags.some((tag) => tag.name === '@tidgi-mini-window')) {
await clearTidgiMiniWindowSettings(scenarioRoot);
}
if (pickle.tags.some((tag) => tag.name === '@ai-setting')) {
await clearAISettings(scenarioRoot);
}
if (pickle.tags.some((tag) => tag.name === '@subwiki')) {
await clearSubWikiRoutingTestData(scenarioRoot);
}
// Clean up hibernation test data - remove wiki2 folder created during tests
if (pickle.tags.some((tag) => tag.name === '@hibernation')) {
await clearHibernationTestData(scenarioRoot);
}
// Clean up move workspace test data - remove wiki-test-moved folder
if (pickle.tags.some((tag) => tag.name === '@move-workspace')) {
const wikiTestMovedPath = path.resolve(scenarioRoot, 'wiki-test-moved');
if (await fs.pathExists(wikiTestMovedPath)) {
await fs.remove(wikiTestMovedPath);
}
}
// Scenario-specific logs are already in the right place, no need to move them
});