diff --git a/tests/collection/close-all-collections/close-all-collections.spec.ts b/tests/collection/close-all-collections/close-all-collections.spec.ts
new file mode 100644
index 000000000..2f458de8b
--- /dev/null
+++ b/tests/collection/close-all-collections/close-all-collections.spec.ts
@@ -0,0 +1,186 @@
+import { execSync } from 'child_process';
+import { test, expect } from '../../../playwright';
+import { Page, ElectronApplication } from '@playwright/test';
+import path from 'path';
+import { openCollectionAndAcceptSandbox } from '../../utils/page/actions';
+import { buildCommonLocators } from '../../utils/page/locators';
+
+/**
+ * Helper function to restart app and get fresh state with locators
+ */
+const restartAppAndGetLocators = async (restartApp: (options?: { initUserDataPath?: string }) => Promise): Promise<{ app: ElectronApplication; page: Page; locators: ReturnType }> => {
+ const app = await restartApp();
+ const page = await app.firstWindow();
+ await page.locator('[data-app-state="loaded"]').waitFor();
+ const locators = buildCommonLocators(page);
+ return { app, page, locators };
+};
+
+test.describe('Close All Collections', () => {
+ test.afterAll(async () => {
+ // Reset the request file to the original state after saving changes
+ execSync(`git checkout -- "${path.join(__dirname, 'fixtures', 'collections', 'collection 1', 'test-request.bru')}"`);
+ });
+
+ test('should show/hide close all icon based on hover state', async ({ pageWithUserData: page }) => {
+ const locators = buildCommonLocators(page);
+
+ await test.step('Verify initial state', async () => {
+ await expect(locators.sidebar.collection('collection 1')).toBeVisible();
+ const closeAllButton = locators.sidebar.closeAllCollectionsButton();
+ await expect(closeAllButton).toHaveCSS('opacity', '0');
+ });
+
+ await test.step('Hover to show icon', async () => {
+ const closeAllButton = locators.sidebar.closeAllCollectionsButton();
+ await locators.sidebar.collectionsContainer().hover();
+ await expect(closeAllButton).toHaveCSS('opacity', '1');
+ });
+
+ await test.step('Move mouse away to hide icon', async () => {
+ const closeAllButton = locators.sidebar.closeAllCollectionsButton();
+ await page.mouse.move(0, 0);
+ await expect(closeAllButton).toHaveCSS('opacity', '0');
+ });
+ });
+
+ test('should handle closing all collections without unsaved changes', async ({ restartApp }) => {
+ const { page, locators } = await restartAppAndGetLocators(restartApp);
+
+ await test.step('Verify collections are visible', async () => {
+ await expect(locators.sidebar.collection('collection 1')).toBeVisible();
+ await expect(locators.sidebar.collection('collection 2')).toBeVisible();
+ });
+
+ await test.step('Cancel closing collections', async () => {
+ // Hover and click close all icon
+ await locators.sidebar.collectionsContainer().hover();
+ await locators.sidebar.closeAllCollectionsButton().click();
+
+ // Verify confirmation modal appears
+ const confirmModal = locators.modal.byTitle('Close all collections');
+ await expect(confirmModal).toBeVisible();
+
+ // Click "Cancel" to dismiss the modal
+ await locators.modal.closeButton().click();
+
+ // Verify collections are still visible
+ await expect(locators.sidebar.collection('collection 1')).toBeVisible();
+ await expect(locators.sidebar.collection('collection 2')).toBeVisible();
+ });
+
+ await test.step('Confirm closing collections', async () => {
+ // Hover and click close all icon again
+ await locators.sidebar.collectionsContainer().hover();
+ await locators.sidebar.closeAllCollectionsButton().click();
+
+ // Verify confirmation modal appears
+ const confirmModal = locators.modal.byTitle('Close all collections');
+ await expect(confirmModal).toBeVisible();
+
+ // Click "Close All" to confirm
+ await locators.modal.button('Close All').click();
+
+ // Verify collections are closed
+ await expect(locators.sidebar.collection('collection 1')).not.toBeVisible();
+ await expect(locators.sidebar.collection('collection 2')).not.toBeVisible();
+ });
+ });
+
+ test('should discard changes and close collections when Discard and Close is clicked', async ({ restartApp }) => {
+ const { page, locators: newLocators } = await restartAppAndGetLocators(restartApp);
+
+ await test.step('Verify collections are visible', async () => {
+ await expect(newLocators.sidebar.collection('collection 1')).toBeVisible();
+ await expect(newLocators.sidebar.collection('collection 2')).toBeVisible();
+ });
+
+ await test.step('Create unsaved changes', async () => {
+ await openCollectionAndAcceptSandbox(page, 'collection 1');
+ await newLocators.sidebar.request('test-request').click();
+
+ const urlContainer = page.locator('#request-url');
+ await expect(urlContainer).toBeVisible();
+
+ const codeMirrorEditor = urlContainer.locator('.CodeMirror');
+ await codeMirrorEditor.click();
+ await page.keyboard.type('modified');
+ });
+
+ await test.step('Trigger close all and discard changes', async () => {
+ await newLocators.sidebar.collectionsContainer().hover();
+ await newLocators.sidebar.closeAllCollectionsButton().click();
+
+ const unsavedChangesModal = newLocators.modal.byTitle('Close all collections');
+ await expect(unsavedChangesModal).toBeVisible();
+ await expect(unsavedChangesModal.getByText('Do you want to save')).toBeVisible();
+
+ await newLocators.modal.button('Discard and Close').click();
+
+ await expect(page.getByText('Closed all collections')).toBeVisible();
+ await expect(newLocators.sidebar.collection('collection 1')).not.toBeVisible();
+ await expect(newLocators.sidebar.collection('collection 2')).not.toBeVisible();
+ });
+
+ await test.step('Restart app to verify changes were discarded', async () => {
+ const { page: restartedPage, locators: restartedLocators } = await restartAppAndGetLocators(restartApp);
+
+ await expect(restartedLocators.sidebar.collection('collection 1')).toBeVisible();
+ await openCollectionAndAcceptSandbox(restartedPage, 'collection 1');
+ await restartedLocators.sidebar.request('test-request').click();
+
+ const urlContainerAfterReopen = restartedPage.locator('#request-url');
+ await expect(urlContainerAfterReopen).toBeVisible();
+ const urlAfterReopen = await urlContainerAfterReopen.locator('.CodeMirror').textContent();
+ expect(urlAfterReopen).not.toContain('modified');
+ });
+ });
+
+ test('should save changes and close collections when Save and Close is clicked', async ({ restartApp }) => {
+ const { page, locators: newLocators } = await restartAppAndGetLocators(restartApp);
+
+ await test.step('Verify collections are visible', async () => {
+ await expect(newLocators.sidebar.collection('collection 1')).toBeVisible();
+ await expect(newLocators.sidebar.collection('collection 2')).toBeVisible();
+ });
+
+ await test.step('Create unsaved changes', async () => {
+ await openCollectionAndAcceptSandbox(page, 'collection 1');
+ await newLocators.sidebar.request('test-request').click();
+
+ const urlContainer = page.locator('#request-url');
+ await expect(urlContainer).toBeVisible();
+
+ const codeMirrorEditor = urlContainer.locator('.CodeMirror');
+ await codeMirrorEditor.click();
+ await page.keyboard.type('modified');
+ });
+
+ await test.step('Trigger close all and save changes', async () => {
+ await newLocators.sidebar.collectionsContainer().hover();
+ await newLocators.sidebar.closeAllCollectionsButton().click();
+
+ const unsavedChangesModal = newLocators.modal.byTitle('Close all collections');
+ await expect(unsavedChangesModal).toBeVisible();
+ await expect(unsavedChangesModal.getByText('Do you want to save')).toBeVisible();
+
+ await newLocators.modal.button('Save and Close').click();
+
+ await expect(newLocators.sidebar.collection('collection 1')).not.toBeVisible();
+ await expect(newLocators.sidebar.collection('collection 2')).not.toBeVisible();
+ });
+
+ await test.step('Restart app to verify changes were saved', async () => {
+ const { page: restartedPage, locators: restartedLocators } = await restartAppAndGetLocators(restartApp);
+
+ await expect(restartedLocators.sidebar.collection('collection 1')).toBeVisible();
+ await openCollectionAndAcceptSandbox(restartedPage, 'collection 1');
+ await restartedLocators.sidebar.request('test-request').click();
+
+ const urlContainerAfterReopen = restartedPage.locator('#request-url');
+ await expect(urlContainerAfterReopen).toBeVisible();
+ const urlAfterReopen = await urlContainerAfterReopen.locator('.CodeMirror').textContent();
+ expect(urlAfterReopen).toContain('modified');
+ });
+ });
+});
diff --git a/tests/collection/close-all-collections/fixtures/collections/collection 1/bruno.json b/tests/collection/close-all-collections/fixtures/collections/collection 1/bruno.json
new file mode 100644
index 000000000..9296360cd
--- /dev/null
+++ b/tests/collection/close-all-collections/fixtures/collections/collection 1/bruno.json
@@ -0,0 +1,6 @@
+{
+ "version": "1",
+ "name": "collection 1",
+ "type": "collection"
+}
+
diff --git a/tests/collection/close-all-collections/fixtures/collections/collection 1/test-request.bru b/tests/collection/close-all-collections/fixtures/collections/collection 1/test-request.bru
new file mode 100644
index 000000000..4b0a586e9
--- /dev/null
+++ b/tests/collection/close-all-collections/fixtures/collections/collection 1/test-request.bru
@@ -0,0 +1,12 @@
+meta {
+ name: test-request
+ type: http
+ seq: 1
+}
+
+get {
+ url: https://jsonplaceholder.typicode.com/posts/1
+ body: none
+ auth: none
+}
+
diff --git a/tests/collection/close-all-collections/fixtures/collections/collection 2/bruno.json b/tests/collection/close-all-collections/fixtures/collections/collection 2/bruno.json
new file mode 100644
index 000000000..61e871772
--- /dev/null
+++ b/tests/collection/close-all-collections/fixtures/collections/collection 2/bruno.json
@@ -0,0 +1,6 @@
+{
+ "version": "1",
+ "name": "collection 2",
+ "type": "collection"
+}
+
diff --git a/tests/collection/close-all-collections/fixtures/collections/collection 2/test-request.bru b/tests/collection/close-all-collections/fixtures/collections/collection 2/test-request.bru
new file mode 100644
index 000000000..5a64399b6
--- /dev/null
+++ b/tests/collection/close-all-collections/fixtures/collections/collection 2/test-request.bru
@@ -0,0 +1,12 @@
+meta {
+ name: test-request
+ type: http
+ seq: 1
+}
+
+get {
+ url: https://jsonplaceholder.typicode.com/users/1
+ body: none
+ auth: none
+}
+
diff --git a/tests/collection/close-all-collections/init-user-data/preferences.json b/tests/collection/close-all-collections/init-user-data/preferences.json
new file mode 100644
index 000000000..e818ec80e
--- /dev/null
+++ b/tests/collection/close-all-collections/init-user-data/preferences.json
@@ -0,0 +1,7 @@
+{
+ "lastOpenedCollections": [
+ "{{projectRoot}}/tests/collection/close-all-collections/fixtures/collections/collection 1",
+ "{{projectRoot}}/tests/collection/close-all-collections/fixtures/collections/collection 2"
+ ]
+}
+
diff --git a/tests/environments/import-environment/global-env-import.spec.ts b/tests/environments/import-environment/global-env-import.spec.ts
index 0327ae830..8e5205296 100644
--- a/tests/environments/import-environment/global-env-import.spec.ts
+++ b/tests/environments/import-environment/global-env-import.spec.ts
@@ -88,6 +88,7 @@ test.describe('Global Environment Import Tests', () => {
.click();
await page.locator('.dropdown-item').filter({ hasText: 'Close' }).click();
await page.locator('.dropdown-item').filter({ hasText: 'Close' }).waitFor({ state: 'detached' });
- await page.getByRole('button', { name: 'Close' }).click();
+ const closeModal = page.getByRole('dialog').filter({ has: page.getByText('Close Collection') });
+ await closeModal.getByRole('button', { name: 'Close' }).click();
});
});
diff --git a/tests/onboarding/sample-collection.spec.ts b/tests/onboarding/sample-collection.spec.ts
index 78d89a38b..5c842c90e 100644
--- a/tests/onboarding/sample-collection.spec.ts
+++ b/tests/onboarding/sample-collection.spec.ts
@@ -101,7 +101,7 @@ test.describe('Onboarding', () => {
await closeOption.click();
// Handle the confirmation dialog - click the 'Close' button to confirm
- const confirmCloseButton = page.getByRole('button', { name: 'Close' });
+ const confirmCloseButton = page.locator('.bruno-modal').getByRole('button', { name: 'Close' });
await expect(confirmCloseButton).toBeVisible();
await confirmCloseButton.click();
diff --git a/tests/utils/page/locators.ts b/tests/utils/page/locators.ts
index dde7bf971..7ee6e660a 100644
--- a/tests/utils/page/locators.ts
+++ b/tests/utils/page/locators.ts
@@ -6,6 +6,7 @@ export const buildCommonLocators = (page: Page) => ({
.locator('.infotip')
.filter({ hasText: /^Save/ }),
sidebar: {
+ collectionsContainer: () => page.getByTestId('collections'),
collection: (name: string) => page.locator('#sidebar-collection-name').filter({ hasText: name }),
folder: (name: string) => page.locator('.collection-item-name').filter({ hasText: name }),
request: (name: string) => page.locator('.collection-item-name').filter({ hasText: name }),
@@ -15,7 +16,8 @@ export const buildCommonLocators = (page: Page) => ({
// Using .locator('..') gets the parent element of the folder's collection-item-name div.
const folderWrapper = page.locator('.collection-item-name').filter({ hasText: folderName }).locator('..');
return folderWrapper.locator('.collection-item-name').filter({ hasText: requestName });
- }
+ },
+ closeAllCollectionsButton: () => page.getByTestId('close-all-collections-button')
},
actions: {
collectionActions: (collectionName: string) =>
@@ -40,7 +42,9 @@ export const buildCommonLocators = (page: Page) => ({
},
modal: {
title: (title: string) => page.locator('.bruno-modal-header-title').filter({ hasText: title }),
- button: (name: string) => page.getByRole('button', { name: name, exact: true })
+ byTitle: (title: string) => page.locator('.bruno-modal').filter({ has: page.locator('.bruno-modal-header-title').filter({ hasText: title }) }),
+ button: (name: string) => page.getByRole('button', { name: name, exact: true }),
+ closeButton: () => page.locator('.bruno-modal').getByTestId('modal-close-button')
},
environment: {
selector: () => page.getByTestId('environment-selector-trigger'),