Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 47 additions & 39 deletions packages/bruno-electron/src/app/collection-watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -552,58 +552,66 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
};

const unlink = (win, pathname, collectionUid, collectionPath) => {
console.log(`watcher unlink: ${pathname}`);

if (isEnvironmentsFolder(pathname, collectionPath)) {
return unlinkEnvironmentFile(win, pathname, collectionUid);
}

const format = getCollectionFormat(collectionPath);
if (hasRequestExtension(pathname, format)) {
const basename = path.basename(pathname);
const dirname = path.dirname(pathname);
try {
console.log(`watcher unlink: ${pathname}`);

if (basename === 'opencollection.yml' && path.normalize(dirname) === path.normalize(collectionPath)) {
return;
if (isEnvironmentsFolder(pathname, collectionPath)) {
return unlinkEnvironmentFile(win, pathname, collectionUid);
}

const file = {
meta: {
collectionUid,
pathname,
name: basename
const format = getCollectionFormat(collectionPath);
if (hasRequestExtension(pathname, format)) {
const basename = path.basename(pathname);
const dirname = path.dirname(pathname);

if (basename === 'opencollection.yml' && path.normalize(dirname) === path.normalize(collectionPath)) {
return;
}
};
win.webContents.send('main:collection-tree-updated', 'unlink', file);

const file = {
meta: {
collectionUid,
pathname,
name: basename
}
};
win.webContents.send('main:collection-tree-updated', 'unlink', file);
}
} catch (err) {
console.error(`Error processing unlink event for: ${pathname}`, err);
}
};

const unlinkDir = async (win, pathname, collectionUid, collectionPath) => {
const envDirectory = path.join(collectionPath, 'environments');

if (path.normalize(pathname) === path.normalize(envDirectory)) {
return;
}
try {
const envDirectory = path.join(collectionPath, 'environments');

const format = getCollectionFormat(collectionPath);
const folderFilePath = path.join(pathname, `folder.${format}`);
if (path.normalize(pathname) === path.normalize(envDirectory)) {
return;
}

let name = path.basename(pathname);
const format = getCollectionFormat(collectionPath);
const folderFilePath = path.join(pathname, `folder.${format}`);

if (fs.existsSync(folderFilePath)) {
let folderFileContent = fs.readFileSync(folderFilePath, 'utf8');
let folderData = await parseFolder(folderFileContent, { format });
name = folderData?.meta?.name || name;
}
let name = path.basename(pathname);

const directory = {
meta: {
collectionUid,
pathname,
name
if (fs.existsSync(folderFilePath)) {
let folderFileContent = fs.readFileSync(folderFilePath, 'utf8');
let folderData = await parseFolder(folderFileContent, { format });
name = folderData?.meta?.name || name;
}
};
win.webContents.send('main:collection-tree-updated', 'unlinkDir', directory);

const directory = {
meta: {
collectionUid,
pathname,
name
}
};
win.webContents.send('main:collection-tree-updated', 'unlinkDir', directory);
} catch (err) {
console.error(`Error processing unlinkDir event for: ${pathname}`, err);
}
};

const onWatcherSetupComplete = (win, watchPath, collectionUid, watcher) => {
Expand Down
42 changes: 42 additions & 0 deletions tests/collection/delete/delete-collection.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import fs from 'fs';
import path from 'path';
import { test, expect } from '../../../playwright';
import { createCollection, createRequest, deleteCollectionFromOverview } from '../../utils/page';

test.describe('Delete collection', () => {
test('Delete collection from workspace overview removes files from disk', async ({ page, createTmpDir }) => {
const collectionName = 'delete-test-collection';
const tmpDir = await createTmpDir(collectionName);
const collectionPath = path.join(tmpDir, collectionName);

// Create a collection with a request
await createCollection(page, collectionName, tmpDir);
await createRequest(page, 'ping', collectionName, { url: 'http://localhost:8081/ping' });

// Verify collection directory exists on disk
expect(fs.existsSync(collectionPath)).toBe(true);

// Capture any uncaught errors during deletion
const pageErrors: Error[] = [];
page.on('pageerror', (error) => pageErrors.push(error));

// Navigate to Workspace and delete collection from overview
await deleteCollectionFromOverview(page, collectionName);

// Verify collection is removed from overview
await expect(
page.locator('.collection-card').filter({ hasText: collectionName })
).not.toBeVisible();

// Verify collection is removed from sidebar
await expect(
page.locator('#sidebar-collection-name').filter({ hasText: collectionName })
).not.toBeVisible();

// Verify collection directory is deleted from disk
expect(fs.existsSync(collectionPath)).toBe(false);

// Verify no uncaught JS errors occurred during deletion
expect(pageErrors).toHaveLength(0);
});
});
37 changes: 37 additions & 0 deletions tests/utils/page/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
for (let i = 0; i < numberOfCollections; i++) {
const firstCollection = page.locator('[data-testid="collections"] .collection-name').first();
await firstCollection.hover();
await firstCollection.locator('.collection-actions .icon').click();

Check failure on line 18 in tests/utils/page/actions.ts

View workflow job for this annotation

GitHub Actions / Detect Flaky Tests

[default] › tests/websockets/variable-interpolation/variable-interpolation.spec.ts:44:7 › WebSocket Variable Interpolation › interpolates variables in WebSocket message content

5) [default] › tests/websockets/variable-interpolation/variable-interpolation.spec.ts:44:7 › WebSocket Variable Interpolation › interpolates variables in WebSocket message content TimeoutError: locator.click: Timeout 30000ms exceeded. Call log: - waiting for locator('[data-testid="collections"] .collection-name').first().locator('.collection-actions .icon') - locator resolved to <svg width="18" height="18" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-dots">…</svg> - attempting click action 2 × waiting for element to be visible, enabled and stable - element is not visible - retrying click action - waiting 20ms 2 × waiting for element to be visible, enabled and stable - element is not visible - retrying click action - waiting 100ms 56 × waiting for element to be visible, enabled and stable - element is not visible - retrying click action - waiting 500ms at tests/utils/page/actions.ts:18 16 | const firstCollection = page.locator('[data-testid="collections"] .collection-name').first(); 17 | await firstCollection.hover(); > 18 | await firstCollection.locator('.collection-actions .icon').click(); | ^ 19 | await page.locator('.dropdown-item').getByText('Remove').click(); 20 | 21 | // Wait for modal to appear - could be either regular remove or drafts confirmation at /home/runner/work/bruno/bruno/tests/utils/page/actions.ts:18:66 at closeAllCollections (/home/runner/work/bruno/bruno/tests/utils/page/actions.ts:12:3) at /home/runner/work/bruno/bruno/tests/websockets/variable-interpolation/variable-interpolation.spec.ts:10:5
await page.locator('.dropdown-item').getByText('Remove').click();

// Wait for modal to appear - could be either regular remove or drafts confirmation
Expand All @@ -27,7 +27,7 @@

if (hasDiscardButton) {
// Drafts modal - click "Discard All and Remove"
await page.getByRole('button', { name: 'Discard All and Remove' }).click();

Check failure on line 30 in tests/utils/page/actions.ts

View workflow job for this annotation

GitHub Actions / Detect Flaky Tests

[default] › tests/request/response-pane-update-when-focused.spec.ts:30:7 › Response pane updates when focused and request is re-sent › Response pane shows new response after re-send with Cmd+Enter while focused in response

4) [default] › tests/request/response-pane-update-when-focused.spec.ts:30:7 › Response pane updates when focused and request is re-sent › Response pane shows new response after re-send with Cmd+Enter while focused in response Error: locator.click: Target page, context or browser has been closed Call log: - waiting for getByRole('button', { name: 'Discard All and Remove' }) - locator resolved to <button type="button">…</button> - attempting click action 2 × waiting for element to be visible, enabled and stable - element is not stable - retrying click action - waiting 20ms 2 × waiting for element to be visible, enabled and stable - element is not stable - retrying click action - waiting 100ms - waiting for element to be visible, enabled and stable - element is not stable - retrying click action - waiting 500ms - waiting for element to be visible, enabled and stable - element was detached from the DOM, retrying at tests/utils/page/actions.ts:30 28 | if (hasDiscardButton) { 29 | // Drafts modal - click "Discard All and Remove" > 30 | await page.getByRole('button', { name: 'Discard All and Remove' }).click(); | ^ 31 | } else { 32 | // Regular modal - click the submit button 33 | await page.locator('.bruno-modal-footer .submit').click(); at /home/runner/work/bruno/bruno/tests/utils/page/actions.ts:30:76 at closeAllCollections (/home/runner/work/bruno/bruno/tests/utils/page/actions.ts:12:3) at /home/runner/work/bruno/bruno/tests/request/response-pane-update-when-focused.spec.ts:27:5

Check failure on line 30 in tests/utils/page/actions.ts

View workflow job for this annotation

GitHub Actions / Playwright E2E Tests

[default] › tests/request/response-pane-update-when-focused.spec.ts:30:7 › Response pane updates when focused and request is re-sent › Response pane shows new response after re-send with Cmd+Enter while focused in response

3) [default] › tests/request/response-pane-update-when-focused.spec.ts:30:7 › Response pane updates when focused and request is re-sent › Response pane shows new response after re-send with Cmd+Enter while focused in response Error: locator.click: Target page, context or browser has been closed Call log: - waiting for getByRole('button', { name: 'Discard All and Remove' }) - locator resolved to <button type="button">…</button> - attempting click action 2 × waiting for element to be visible, enabled and stable - element is not stable - retrying click action - waiting 20ms 2 × waiting for element to be visible, enabled and stable - element is not stable - retrying click action - waiting 100ms - waiting for element to be visible, enabled and stable - element is not stable - retrying click action - waiting 500ms - waiting for element to be visible, enabled and stable - element was detached from the DOM, retrying at tests/utils/page/actions.ts:30 28 | if (hasDiscardButton) { 29 | // Drafts modal - click "Discard All and Remove" > 30 | await page.getByRole('button', { name: 'Discard All and Remove' }).click(); | ^ 31 | } else { 32 | // Regular modal - click the submit button 33 | await page.locator('.bruno-modal-footer .submit').click(); at /home/runner/work/bruno/bruno/tests/utils/page/actions.ts:30:76 at closeAllCollections (/home/runner/work/bruno/bruno/tests/utils/page/actions.ts:12:3) at /home/runner/work/bruno/bruno/tests/request/response-pane-update-when-focused.spec.ts:27:5
} else {
// Regular modal - click the submit button
await page.locator('.bruno-modal-footer .submit').click();
Expand Down Expand Up @@ -89,7 +89,7 @@
}
await createCollectionModal.getByRole('button', { name: 'Create', exact: true }).click();

await createCollectionModal.waitFor({ state: 'detached', timeout: 15000 });

Check failure on line 92 in tests/utils/page/actions.ts

View workflow job for this annotation

GitHub Actions / Playwright E2E Tests

[default] › tests/workspace/close-tab-stays-in-workspace.spec.ts:23:7 › Close tab stays in workspace › after closing last request tab in WorkspaceB

4) [default] › tests/workspace/close-tab-stays-in-workspace.spec.ts:23:7 › Close tab stays in workspace › after closing last request tab in WorkspaceB, active tab is not from WorkspaceA and workspace stays WorkspaceB › Create ColB/ReqB in WorkspaceB and open ReqB › Create collection "ColB" TimeoutError: locator.waitFor: Timeout 15000ms exceeded. Call log: - waiting for locator('.bruno-modal-card').filter({ hasText: 'Create Collection' }) to be detached 35 × locator resolved to visible <div role="dialog" aria-labelledby="modal-title" class="bruno-modal-card modal-md" aria-describedby="modal-description">…</div> at tests/utils/page/actions.ts:92 90 | await createCollectionModal.getByRole('button', { name: 'Create', exact: true }).click(); 91 | > 92 | await createCollectionModal.waitFor({ state: 'detached', timeout: 15000 }); | ^ 93 | await page.waitForTimeout(200); 94 | await openCollection(page, collectionName); 95 | }); at /home/runner/work/bruno/bruno/tests/utils/page/actions.ts:92:33 at createCollection (/home/runner/work/bruno/bruno/tests/utils/page/actions.ts:67:3) at /home/runner/work/bruno/bruno/tests/workspace/close-tab-stays-in-workspace.spec.ts:63:9 at /home/runner/work/bruno/bruno/tests/workspace/close-tab-stays-in-workspace.spec.ts:62:7
await page.waitForTimeout(200);
await openCollection(page, collectionName);
});
Expand Down Expand Up @@ -323,6 +323,42 @@
});
};

/**
* Delete a collection permanently from disk via the workspace overview page
* @param page - The page object
* @param collectionName - The name of the collection to delete
* @returns void
*/
const deleteCollectionFromOverview = async (page: Page, collectionName: string) => {
await test.step(`Delete collection "${collectionName}" from workspace overview`, async () => {
// Navigate to workspace overview
await page.locator('.home-button').click();
const overviewTab = page.locator('.request-tab').filter({ hasText: 'Overview' });
await overviewTab.click();

// Find the collection card and open its menu
const collectionCard = page.locator('.collection-card').filter({ hasText: collectionName });
await collectionCard.waitFor({ state: 'visible', timeout: 5000 });
await collectionCard.locator('.collection-menu').click();

// Click Delete from the dropdown
await page.locator('.dropdown-item').filter({ hasText: 'Delete' }).click();

// Wait for delete confirmation modal
const deleteModal = page.locator('.bruno-modal').filter({ hasText: 'Delete Collection' });
await deleteModal.waitFor({ state: 'visible', timeout: 5000 });

// Type 'delete' to confirm
await deleteModal.locator('#delete-confirm-input').fill('delete');

// Click the Delete button
await deleteModal.getByRole('button', { name: 'Delete', exact: true }).click();

// Wait for modal to close
await deleteModal.waitFor({ state: 'hidden', timeout: 10000 });
});
};

/**
* Import a collection from a file
* @param page - The page object
Expand Down Expand Up @@ -1020,6 +1056,7 @@
createTransientRequest,
fillRequestUrl,
deleteRequest,
deleteCollectionFromOverview,
importCollection,
removeCollection,
createFolder,
Expand Down
Loading