diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
index 9394bea69..acca2e68f 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
@@ -744,6 +744,7 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
))}
{
const dispatch = useDispatch();
const isLoading = collection.isLoading;
const collectionRef = useRef(null);
- // Only count persisted items; transients don't affect empty state
- const itemCount = collection.items?.filter((i) => !i.isTransient).length || 0;
+ // Only count persisted requests and folders; transients and file items
+ // (bruno.json, .js scripts) don't affect empty state
+ const itemCount = collection.items?.filter((i) => !i.isTransient && (isItemARequest(i) || isItemAFolder(i))).length || 0;
const isCollectionFocused = useSelector(isTabForItemActive({ itemUid: collection.uid }));
const { hasCopiedItems } = useSelector((state) => state.app.clipboard);
@@ -533,6 +534,7 @@ const Collection = ({ collection, searchText }) => {
{
+ let locators: ReturnType;
+
+ test.beforeAll(async ({ pageWithUserData: page }) => {
+ locators = buildCommonLocators(page);
+ });
+
+ test.afterAll(async ({ pageWithUserData: page }) => {
+ await closeAllCollections(page);
+ });
+
+ // Scope an assertion to a single collection — pageWithUserData reuses one app
+ // across the describe block, and multiple expanded collections would otherwise
+ // make `getByTestId('add-request-cta')` match more than one element.
+ const collectionScope = (page: Page, name: string) => page.locator(`#collection-${name}`);
+
+ const expandCollection = async (name: string) => {
+ const collection = locators.sidebar.collection(name);
+ await collection.waitFor({ state: 'visible' });
+ await collection.click();
+ };
+
+ // Empty collection — CTA should appear
+
+ test('should show CTA for an empty .bru collection', async ({ pageWithUserData: page }) => {
+ await test.step('Expand empty-bru collection', async () => {
+ await expandCollection('empty-bru');
+ });
+
+ await test.step('Verify CTA is visible at collection root', async () => {
+ await expect(collectionScope(page, 'empty-bru').getByTestId('add-request-cta')).toBeVisible();
+ });
+ });
+
+ test('should show CTA for an empty .yml collection', async ({ pageWithUserData: page }) => {
+ await test.step('Expand empty-yml collection', async () => {
+ await expandCollection('empty-yml');
+ });
+
+ await test.step('Verify CTA is visible at collection root', async () => {
+ await expect(collectionScope(page, 'empty-yml').getByTestId('add-request-cta')).toBeVisible();
+ });
+ });
+
+ // Collection containing only a .js script — CTA should still appear
+
+ test('should show CTA for a .bru collection containing only a .js script', async ({ pageWithUserData: page }) => {
+ await test.step('Expand bru-with-js collection', async () => {
+ await expandCollection('bru-with-js');
+ });
+
+ await test.step('Verify CTA is visible at collection root', async () => {
+ await expect(collectionScope(page, 'bru-with-js').getByTestId('add-request-cta')).toBeVisible();
+ });
+ });
+
+ test('should show CTA for a .yml collection containing only a .js script', async ({ pageWithUserData: page }) => {
+ await test.step('Expand yml-with-js collection', async () => {
+ await expandCollection('yml-with-js');
+ });
+
+ await test.step('Verify CTA is visible at collection root', async () => {
+ await expect(collectionScope(page, 'yml-with-js').getByTestId('add-request-cta')).toBeVisible();
+ });
+ });
+
+ // Collection has user content — root CTA should be hidden
+
+ test('should hide CTA when .bru collection contains a request', async ({ pageWithUserData: page }) => {
+ await test.step('Expand bru-with-request collection', async () => {
+ await expandCollection('bru-with-request');
+ await expect(locators.sidebar.request('bru-echo')).toBeVisible();
+ });
+
+ await test.step('Verify CTA is not rendered at collection root', async () => {
+ await expect(collectionScope(page, 'bru-with-request').getByTestId('add-request-cta')).toHaveCount(0);
+ });
+ });
+
+ test('should hide CTA when .yml collection contains a request', async ({ pageWithUserData: page }) => {
+ await test.step('Expand yml-with-request collection', async () => {
+ await expandCollection('yml-with-request');
+ await expect(locators.sidebar.request('yml-echo')).toBeVisible();
+ });
+
+ await test.step('Verify CTA is not rendered at collection root', async () => {
+ await expect(collectionScope(page, 'yml-with-request').getByTestId('add-request-cta')).toHaveCount(0);
+ });
+ });
+
+ test('should hide root CTA when .bru collection contains a folder', async ({ pageWithUserData: page }) => {
+ await test.step('Expand bru-folder-with-js collection', async () => {
+ await expandCollection('bru-folder-with-js');
+ await expect(locators.sidebar.folder('bru-scripts')).toBeVisible();
+ });
+
+ await test.step('Verify CTA is not rendered at collection root', async () => {
+ await expect(collectionScope(page, 'bru-folder-with-js').getByTestId('add-request-cta')).toHaveCount(0);
+ });
+ });
+
+ test('should hide root CTA when .yml collection contains a folder', async ({ pageWithUserData: page }) => {
+ await test.step('Expand yml-with-folder collection', async () => {
+ await expandCollection('yml-with-folder');
+ await expect(locators.sidebar.folder('yml-scripts')).toBeVisible();
+ });
+
+ await test.step('Verify CTA is not rendered at collection root', async () => {
+ await expect(collectionScope(page, 'yml-with-folder').getByTestId('add-request-cta')).toHaveCount(0);
+ });
+ });
+
+ // Folder containing only a .js script — folder CTA should appear
+
+ test('should show folder CTA when a .bru folder contains only a .js script', async ({ pageWithUserData: page }) => {
+ await test.step('Expand bru-folder-with-js collection and the bru-scripts folder', async () => {
+ await expandCollection('bru-folder-with-js');
+ const folder = locators.sidebar.folder('bru-scripts');
+ await folder.waitFor({ state: 'visible' });
+ await folder.click();
+ });
+
+ await test.step('Verify folder-level CTA is visible', async () => {
+ await expect(collectionScope(page, 'bru-folder-with-js').getByTestId('add-request-cta-folder')).toBeVisible();
+ });
+ });
+
+ test('should show folder CTA when a .yml folder contains only a .js script', async ({ pageWithUserData: page }) => {
+ await test.step('Expand yml-with-folder collection and the yml-scripts folder', async () => {
+ await expandCollection('yml-with-folder');
+ const folder = locators.sidebar.folder('yml-scripts');
+ await folder.waitFor({ state: 'visible' });
+ await folder.click();
+ });
+
+ await test.step('Verify folder-level CTA is visible', async () => {
+ await expect(collectionScope(page, 'yml-with-folder').getByTestId('add-request-cta-folder')).toBeVisible();
+ });
+ });
+});
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/bruno.json b/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/bruno.json
new file mode 100644
index 000000000..6b9d08a47
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/bruno.json
@@ -0,0 +1,5 @@
+{
+ "version": "1",
+ "name": "bru-folder-with-js",
+ "type": "collection"
+}
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/collection.bru b/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/collection.bru
new file mode 100644
index 000000000..fd6cf0938
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/collection.bru
@@ -0,0 +1,3 @@
+meta {
+ name: bru-folder-with-js
+}
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/scripts/folder.bru b/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/scripts/folder.bru
new file mode 100644
index 000000000..ff08ad7e9
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/scripts/folder.bru
@@ -0,0 +1,4 @@
+meta {
+ name: bru-scripts
+ seq: 1
+}
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/scripts/helper.js b/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/scripts/helper.js
new file mode 100644
index 000000000..c62d994a5
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/bru-folder-with-js/scripts/helper.js
@@ -0,0 +1 @@
+// placeholder for the empty-state CTA test
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-js/bruno.json b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-js/bruno.json
new file mode 100644
index 000000000..895beeadc
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-js/bruno.json
@@ -0,0 +1,5 @@
+{
+ "version": "1",
+ "name": "bru-with-js",
+ "type": "collection"
+}
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-js/collection.bru b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-js/collection.bru
new file mode 100644
index 000000000..11fbbedb9
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-js/collection.bru
@@ -0,0 +1,3 @@
+meta {
+ name: bru-with-js
+}
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-js/helper.js b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-js/helper.js
new file mode 100644
index 000000000..c62d994a5
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-js/helper.js
@@ -0,0 +1 @@
+// placeholder for the empty-state CTA test
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-request/bruno.json b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-request/bruno.json
new file mode 100644
index 000000000..d90b00757
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-request/bruno.json
@@ -0,0 +1,5 @@
+{
+ "version": "1",
+ "name": "bru-with-request",
+ "type": "collection"
+}
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-request/collection.bru b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-request/collection.bru
new file mode 100644
index 000000000..519ec32f2
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-request/collection.bru
@@ -0,0 +1,3 @@
+meta {
+ name: bru-with-request
+}
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-request/echo.bru b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-request/echo.bru
new file mode 100644
index 000000000..44ccc6ff9
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/bru-with-request/echo.bru
@@ -0,0 +1,9 @@
+meta {
+ name: bru-echo
+ type: http
+ seq: 1
+}
+
+get {
+ url: https://echo.usebruno.com
+}
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/empty-bru/bruno.json b/tests/sidebar/empty-state-cta/fixtures/collections/empty-bru/bruno.json
new file mode 100644
index 000000000..6b3431e54
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/empty-bru/bruno.json
@@ -0,0 +1,5 @@
+{
+ "version": "1",
+ "name": "empty-bru",
+ "type": "collection"
+}
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/empty-bru/collection.bru b/tests/sidebar/empty-state-cta/fixtures/collections/empty-bru/collection.bru
new file mode 100644
index 000000000..06e4392c1
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/empty-bru/collection.bru
@@ -0,0 +1,3 @@
+meta {
+ name: empty-bru
+}
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/empty-yml/opencollection.yml b/tests/sidebar/empty-state-cta/fixtures/collections/empty-yml/opencollection.yml
new file mode 100644
index 000000000..17b8e4890
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/empty-yml/opencollection.yml
@@ -0,0 +1,3 @@
+opencollection: "1.0.0"
+info:
+ name: empty-yml
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-folder/opencollection.yml b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-folder/opencollection.yml
new file mode 100644
index 000000000..7cc4e5318
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-folder/opencollection.yml
@@ -0,0 +1,3 @@
+opencollection: "1.0.0"
+info:
+ name: yml-with-folder
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-folder/scripts/folder.yml b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-folder/scripts/folder.yml
new file mode 100644
index 000000000..c4db2154f
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-folder/scripts/folder.yml
@@ -0,0 +1,3 @@
+info:
+ name: yml-scripts
+ seq: 1
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-folder/scripts/helper.js b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-folder/scripts/helper.js
new file mode 100644
index 000000000..c62d994a5
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-folder/scripts/helper.js
@@ -0,0 +1 @@
+// placeholder for the empty-state CTA test
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-js/helper.js b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-js/helper.js
new file mode 100644
index 000000000..c62d994a5
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-js/helper.js
@@ -0,0 +1 @@
+// placeholder for the empty-state CTA test
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-js/opencollection.yml b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-js/opencollection.yml
new file mode 100644
index 000000000..7e0808251
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-js/opencollection.yml
@@ -0,0 +1,3 @@
+opencollection: "1.0.0"
+info:
+ name: yml-with-js
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-request/echo.yml b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-request/echo.yml
new file mode 100644
index 000000000..71b5041d5
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-request/echo.yml
@@ -0,0 +1,8 @@
+info:
+ name: yml-echo
+ type: http
+ seq: 1
+
+http:
+ method: GET
+ url: https://echo.usebruno.com
diff --git a/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-request/opencollection.yml b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-request/opencollection.yml
new file mode 100644
index 000000000..905eb62d4
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/fixtures/collections/yml-with-request/opencollection.yml
@@ -0,0 +1,3 @@
+opencollection: "1.0.0"
+info:
+ name: yml-with-request
diff --git a/tests/sidebar/empty-state-cta/init-user-data/preferences.json b/tests/sidebar/empty-state-cta/init-user-data/preferences.json
new file mode 100644
index 000000000..9cce3d367
--- /dev/null
+++ b/tests/sidebar/empty-state-cta/init-user-data/preferences.json
@@ -0,0 +1,18 @@
+{
+ "lastOpenedCollections": [
+ "{{collectionPath}}/empty-bru",
+ "{{collectionPath}}/empty-yml",
+ "{{collectionPath}}/bru-with-js",
+ "{{collectionPath}}/yml-with-js",
+ "{{collectionPath}}/bru-with-request",
+ "{{collectionPath}}/yml-with-request",
+ "{{collectionPath}}/bru-folder-with-js",
+ "{{collectionPath}}/yml-with-folder"
+ ],
+ "preferences": {
+ "onboarding": {
+ "hasLaunchedBefore": true,
+ "hasSeenWelcomeModal": true
+ }
+ }
+}