diff --git a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js
index 0cd73b96081..9958b1e5d9b 100644
--- a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js
+++ b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js
@@ -52,7 +52,7 @@ const MultipartFormParams = ({ item, collection }) => {
}, [dispatch, collection.uid, item.uid]);
const handleBrowseFiles = useCallback((row, onChange) => {
- dispatch(browseFiles())
+ dispatch(browseFiles([], ['multiSelections']))
.then((filePaths) => {
const processedPaths = filePaths.map((filePath) => {
const collectionDir = collection.pathname;
diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js
index ef327f9a2b9..9c800900e14 100644
--- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js
+++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js
@@ -48,7 +48,7 @@ const ResponseExampleMultipartFormParams = ({ item, collection, exampleUid, edit
const handleBrowseFiles = useCallback((row, onChange) => {
if (!editMode) return;
- dispatch(browseFiles())
+ dispatch(browseFiles([], ['multiSelections']))
.then((filePaths) => {
const processedPaths = filePaths.map((filePath) => {
const collectionDir = collection.pathname;
@@ -212,11 +212,11 @@ const ResponseExampleMultipartFormParams = ({ item, collection, exampleUid, edit
{}}
+ onSave={() => { }}
theme={storedTheme}
value={value || ''}
onChange={(newValue) => handleValueChange(row, newValue, onChange)}
- onRun={() => {}}
+ onRun={() => { }}
allowNewlines={true}
collection={collection}
item={item}
@@ -243,12 +243,12 @@ const ResponseExampleMultipartFormParams = ({ item, collection, exampleUid, edit
readOnly: !editMode,
render: ({ value, onChange }) => (
{}}
+ onSave={() => { }}
theme={storedTheme}
placeholder={!value ? 'Auto' : ''}
value={value || ''}
onChange={onChange}
- onRun={() => {}}
+ onRun={() => { }}
collection={collection}
readOnly={!editMode}
/>
diff --git a/tests/request/multipart-form/multipart-form-multi-file-select.spec.ts b/tests/request/multipart-form/multipart-form-multi-file-select.spec.ts
new file mode 100644
index 00000000000..157d83620e9
--- /dev/null
+++ b/tests/request/multipart-form/multipart-form-multi-file-select.spec.ts
@@ -0,0 +1,138 @@
+import { test, expect } from '../../../playwright';
+import {
+ closeAllCollections,
+ createCollection,
+ createRequest,
+ openCollection,
+ openRequest,
+ saveRequest,
+ selectRequestPaneTab
+} from '../../utils/page';
+import { buildCommonLocators } from '../../utils/page/locators';
+import * as fs from 'fs';
+import * as path from 'path';
+
+test.describe.serial('Multipart Form - Multi-File Selection', () => {
+ let tmpDir: string;
+ let testFile1Path: string;
+ let testFile2Path: string;
+ let testFile3Path: string;
+
+ test.afterEach(async ({ electronApp }) => {
+ await electronApp.evaluate(({ dialog }) => {
+ if ((dialog as any).__originalShowOpenDialog) {
+ dialog.showOpenDialog = (dialog as any).__originalShowOpenDialog;
+ }
+ });
+ });
+
+ test.afterAll(async ({ page }) => {
+ await closeAllCollections(page);
+ });
+
+ test.beforeAll(async ({ page, electronApp, createTmpDir }) => {
+ tmpDir = await createTmpDir('multipart-multi-file-select');
+
+ testFile1Path = path.join(tmpDir, 'file1.txt');
+ testFile2Path = path.join(tmpDir, 'file2.txt');
+ testFile3Path = path.join(tmpDir, 'file3.pdf');
+
+ await fs.promises.writeFile(testFile1Path, '1');
+ await fs.promises.writeFile(testFile2Path, '2');
+ await fs.promises.writeFile(testFile3Path, '3');
+
+ await electronApp.evaluate(({ dialog }, filePaths: string[]) => {
+ (dialog as any).__originalShowOpenDialog = dialog.showOpenDialog;
+ dialog.showOpenDialog = async () => ({
+ canceled: false,
+ filePaths
+ });
+ }, [testFile1Path, testFile2Path, testFile3Path]);
+
+ await test.step('Create collection and request', async () => {
+ await createCollection(page, 'multipart-multi-file', tmpDir);
+ await createRequest(page, 'test-multi-file', 'multipart-multi-file', {
+ url: 'https://testbench-sanity.usebruno.com/api/echo/json',
+ method: 'POST',
+ inFolder: false
+ });
+ });
+
+ await test.step('Open request and set multipart', async () => {
+ await openCollection(page, 'multipart-multi-file');
+ await openRequest(page, 'multipart-multi-file', 'test-multi-file', { persist: true });
+
+ await selectRequestPaneTab(page, 'Body');
+ const locators = buildCommonLocators(page);
+ await locators.request.bodyModeSelector().click();
+ await page.locator('.dropdown-item').filter({ hasText: 'Multipart Form' }).click();
+ });
+ });
+
+ test('should select multiple files', async ({ page }) => {
+ const table = buildCommonLocators(page).table('editable-table');
+ const row = table.allRows().first();
+
+ await test.step('Enter key and upload', async () => {
+ await row.locator('td').nth(1).locator('input').fill('attachments');
+ await page.keyboard.press('Tab');
+
+ const uploadBtn = row.locator('.upload-btn');
+ await expect(uploadBtn).toBeVisible();
+ await uploadBtn.click();
+ });
+
+ await test.step('Verify file count', async () => {
+ const fileCell = row.locator('.file-value-cell');
+ await expect(fileCell).toContainText('3 file(s)');
+ });
+
+ await saveRequest(page);
+ });
+
+ test('should show filename for single file', async ({ page, electronApp }) => {
+ const table = buildCommonLocators(page).table('editable-table');
+ const row = table.allRows().last();
+
+ await electronApp.evaluate(({ dialog }, filePath: string) => {
+ if ((dialog as any).__originalShowOpenDialog === undefined) {
+ (dialog as any).__originalShowOpenDialog = dialog.showOpenDialog;
+ }
+ dialog.showOpenDialog = async () => ({
+ canceled: false,
+ filePaths: [filePath]
+ });
+ }, testFile1Path);
+
+ await test.step('Upload single file', async () => {
+ await row.locator('td').nth(1).locator('input').fill('single');
+ await page.keyboard.press('Tab');
+
+ await row.locator('.upload-btn').click();
+ });
+
+ await test.step('Verify filename', async () => {
+ const fileCell = row.locator('.file-value-cell');
+ await expect(fileCell).toContainText('file1.txt');
+ });
+
+ await saveRequest(page);
+ });
+
+ test('should clear files', async ({ page }) => {
+ const table = buildCommonLocators(page).table('editable-table');
+ const row = table.allRows().first();
+
+ await test.step('Clear files', async () => {
+ const clearBtn = row.locator('.clear-file-btn');
+ await expect(clearBtn).toBeVisible();
+ await clearBtn.click();
+ });
+
+ await test.step('Verify cleared', async () => {
+ await expect(row.locator('.upload-btn')).toBeVisible();
+ });
+
+ await saveRequest(page);
+ });
+});
diff --git a/tests/request/multipart-form/multipart-form-file-select.spec.ts b/tests/request/multipart-form/multipart-form-single-file-select.spec.ts
similarity index 100%
rename from tests/request/multipart-form/multipart-form-file-select.spec.ts
rename to tests/request/multipart-form/multipart-form-single-file-select.spec.ts