move: import setting into import collection modal (#5929)

This commit is contained in:
Pooja
2025-11-12 11:36:21 +05:30
committed by GitHub
parent f439f2de9a
commit 9e19244665
26 changed files with 344 additions and 282 deletions

View File

@@ -3,13 +3,11 @@ import { IconFileImport } from '@tabler/icons';
import { toastError } from 'utils/common/error';
import Modal from 'components/Modal';
import jsyaml from 'js-yaml';
import { postmanToBruno, isPostmanCollection } from 'utils/importers/postman-collection';
import { convertInsomniaToBruno, isInsomniaCollection } from 'utils/importers/insomnia-collection';
import { convertOpenapiToBruno, isOpenApiSpec } from 'utils/importers/openapi-collection';
import { isPostmanCollection } from 'utils/importers/postman-collection';
import { isInsomniaCollection } from 'utils/importers/insomnia-collection';
import { isOpenApiSpec } from 'utils/importers/openapi-collection';
import { isWSDLCollection } from 'utils/importers/wsdl-collection';
import { processBrunoCollection } from 'utils/importers/bruno-collection';
import { wsdlToBruno } from '@usebruno/converters';
import ImportSettings from 'components/Sidebar/ImportSettings';
import { isBrunoCollection } from 'utils/importers/bruno-collection';
import FullscreenLoader from './FullscreenLoader/index';
const convertFileToObject = async (file) => {
@@ -38,9 +36,6 @@ const convertFileToObject = async (file) => {
const ImportCollection = ({ onClose, handleSubmit }) => {
const [isLoading, setIsLoading] = useState(false);
const [dragActive, setDragActive] = useState(false);
const [showImportSettings, setShowImportSettings] = useState(false);
const [openApiData, setOpenApiData] = useState(null);
const [groupingType, setGroupingType] = useState('tags');
const fileInputRef = useRef(null);
const handleDrag = (e) => {
@@ -58,16 +53,6 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
}
};
const handleImportSettings = () => {
try {
const collection = convertOpenapiToBruno(openApiData, { groupBy: groupingType });
handleSubmit({ collection });
} catch (err) {
console.error(err);
toastError(err, 'Failed to process OpenAPI specification');
}
};
const processFile = async (file) => {
setIsLoading(true);
try {
@@ -77,26 +62,23 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
throw new Error('Failed to parse file content');
}
// Check if it's an OpenAPI spec and show settings
let type = null;
if (isOpenApiSpec(data)) {
setOpenApiData(data);
setIsLoading(false);
setShowImportSettings(true);
return;
}
let collection;
if (isWSDLCollection(data)) {
collection = await wsdlToBruno(data);
type = 'openapi';
} else if (isWSDLCollection(data)) {
type = 'wsdl';
} else if (isPostmanCollection(data)) {
collection = await postmanToBruno(data);
type = 'postman';
} else if (isInsomniaCollection(data)) {
collection = convertInsomniaToBruno(data);
type = 'insomnia';
} else if (isBrunoCollection(data)) {
type = 'bruno';
} else {
collection = await processBrunoCollection(data);
throw new Error('Unsupported collection format');
}
handleSubmit({ collection });
handleSubmit({ rawData: data, type });
} catch (err) {
toastError(err, 'Import collection failed');
} finally {
@@ -140,17 +122,6 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
'application/xml'
];
if (showImportSettings) {
return (
<ImportSettings
groupingType={groupingType}
setGroupingType={setGroupingType}
onClose={onClose}
onConfirm={handleImportSettings}
/>
);
}
return (
<Modal size="sm" title="Import Collection" hideFooter={true} handleCancel={onClose} dataTestId="import-collection-modal">
<div className="flex flex-col">

View File

@@ -1,15 +1,94 @@
import React, { useRef, useEffect, useState } from 'react';
import React, { useRef, useEffect, useState, forwardRef } from 'react';
import { useDispatch } from 'react-redux';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { IconCaretDown } from '@tabler/icons';
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
import { postmanToBruno } from 'utils/importers/postman-collection';
import { convertInsomniaToBruno } from 'utils/importers/insomnia-collection';
import { convertOpenapiToBruno } from 'utils/importers/openapi-collection';
import { processBrunoCollection } from 'utils/importers/bruno-collection';
import { wsdlToBruno } from '@usebruno/converters';
import { toastError } from 'utils/common/error';
import Modal from 'components/Modal';
import Help from 'components/Help';
import Dropdown from 'components/Dropdown';
import StyledWrapper from './StyledWrapper';
// Extract collection name from raw data
const getCollectionName = (format, rawData) => {
if (!rawData) return 'Collection';
const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) => {
switch (format) {
case 'openapi':
return rawData.info?.title || 'OpenAPI Collection';
case 'postman':
return rawData.info?.name || rawData.collection?.info?.name || 'Postman Collection';
case 'insomnia':
// For Insomnia v4 format, name is in the workspace resource
if (rawData.resources && Array.isArray(rawData.resources)) {
const workspace = rawData.resources.find((r) => r._type === 'workspace');
if (workspace?.name) {
return workspace.name;
}
}
// Fallback to root name property
return rawData.name || 'Insomnia Collection';
case 'bruno':
return rawData.name || 'Bruno Collection';
case 'wsdl':
return 'WSDL Collection';
default:
return 'Collection';
}
};
// Convert raw data to Bruno collection format
const convertCollection = async (format, rawData, groupingType) => {
try {
let collection;
switch (format) {
case 'openapi':
collection = convertOpenapiToBruno(rawData, { groupBy: groupingType });
break;
case 'wsdl':
collection = await wsdlToBruno(rawData);
break;
case 'postman':
collection = await postmanToBruno(rawData);
break;
case 'insomnia':
collection = convertInsomniaToBruno(rawData);
break;
case 'bruno':
collection = await processBrunoCollection(rawData);
break;
default:
throw new Error('Unknown collection format');
}
return collection;
} catch (err) {
console.error('Conversion error:', err);
toastError(err, 'Failed to convert collection');
throw err;
}
};
const groupingOptions = [
{ value: 'tags', label: 'Tags', description: 'Group requests by OpenAPI tags', testId: 'grouping-option-tags' },
{ value: 'path', label: 'Paths', description: 'Group requests by URL path structure', testId: 'grouping-option-path' }
];
const ImportCollectionLocation = ({ onClose, handleSubmit, rawData, format }) => {
const inputRef = useRef();
const dispatch = useDispatch();
const [groupingType, setGroupingType] = useState('tags');
const dropdownTippyRef = useRef();
const isOpenApi = format === 'openapi';
const collectionName = getCollectionName(format, rawData);
const formik = useFormik({
enableReinitialize: true,
@@ -22,10 +101,27 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) =>
.max(500, 'must be 500 characters or less')
.required('Location is required')
}),
onSubmit: (values) => {
handleSubmit(values.collectionLocation);
onSubmit: async (values) => {
const convertedCollection = await convertCollection(format, rawData, groupingType);
handleSubmit(convertedCollection, values.collectionLocation);
}
});
const onDropdownCreate = (ref) => {
dropdownTippyRef.current = ref;
};
const GroupingDropdownIcon = forwardRef((props, ref) => {
const selectedOption = groupingOptions.find((option) => option.value === groupingType);
return (
<div ref={ref} className="flex items-center justify-between w-full current-group" data-testid="grouping-dropdown">
<div>
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">{selectedOption.label}</div>
</div>
<IconCaretDown size={16} className="text-gray-400 ml-[0.25rem]" fill="currentColor" />
</div>
);
});
const browse = () => {
dispatch(browseDirectory())
.then((dirPath) => {
@@ -48,53 +144,89 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) =>
const onSubmit = () => formik.handleSubmit();
return (
<Modal size="sm" title="Import Collection" confirmText="Import" handleConfirm={onSubmit} handleCancel={onClose} dataTestId="import-collection-location-modal">
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
<div>
<label htmlFor="collectionName" className="block font-semibold">
Name
</label>
<div className="mt-2">{collectionName}</div>
<>
<label htmlFor="collectionLocation" className="block font-semibold mt-3 flex items-center">
Location
<Help>
<p>
Bruno stores your collections on your computer's filesystem.
</p>
<p className="mt-2">
Choose the location where you want to store this collection.
</p>
</Help>
<StyledWrapper>
<Modal
size="sm"
title="Import Collection"
confirmText="Import"
handleConfirm={onSubmit}
handleCancel={onClose}
dataTestId="import-collection-location-modal"
>
<form className="bruno-form" onSubmit={(e) => e.preventDefault()}>
<div>
<label htmlFor="collectionName" className="block font-semibold">
Name
</label>
<input
id="collection-location"
type="text"
name="collectionLocation"
className="block textbox mt-2 w-full cursor-pointer"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.collectionLocation || ''}
onClick={browse}
onChange={e => {
formik.setFieldValue('collectionLocation', e.target.value);
}}
/>
</>
{formik.touched.collectionLocation && formik.errors.collectionLocation ? (
<div className="text-red-500">{formik.errors.collectionLocation}</div>
) : null}
<div className="mt-2">{collectionName}</div>
<div className="mt-1">
<span className="text-link cursor-pointer hover:underline" onClick={browse}>
Browse
</span>
<>
<label htmlFor="collectionLocation" className="font-semibold mt-4 flex items-center">
Location
<Help>
<p>Bruno stores your collections on your computer's filesystem.</p>
<p className="mt-2">Choose the location where you want to store this collection.</p>
</Help>
</label>
<input
id="collection-location"
type="text"
name="collectionLocation"
className="block textbox mt-2 w-full cursor-pointer"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.collectionLocation || ''}
onClick={browse}
onChange={(e) => {
formik.setFieldValue('collectionLocation', e.target.value);
}}
/>
</>
{formik.touched.collectionLocation && formik.errors.collectionLocation ? (
<div className="text-red-500">{formik.errors.collectionLocation}</div>
) : null}
<div className="mt-1">
<span className="text-link cursor-pointer hover:underline" onClick={browse}>
Browse
</span>
</div>
</div>
</div>
</form>
</Modal>
{isOpenApi && (
<div className="mt-4 flex gap-4 items-center">
<div>
<label htmlFor="groupingType" className="block font-semibold mt-4">
Folder arrangement
</label>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1 mb-2">
Select whether to create folders according to the spec's paths or tags.
</p>
</div>
<div className="relative">
<Dropdown onCreate={onDropdownCreate} icon={<GroupingDropdownIcon />} placement="bottom-start">
{groupingOptions.map((option) => (
<div
key={option.value}
className="dropdown-item"
data-testid={option.testId}
onClick={() => {
dropdownTippyRef?.current?.hide();
setGroupingType(option.value);
}}
>
{option.label}
</div>
))}
</Dropdown>
</div>
</div>
)}
</form>
</Modal>
</StyledWrapper>
);
};

View File

@@ -1,84 +0,0 @@
import React, { useRef, forwardRef } from 'react';
import { IconCaretDown } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import Modal from 'components/Modal';
import Portal from 'components/Portal';
import StyledWrapper from './StyledWrapper';
const groupingOptions = [
{ value: 'tags', label: 'Tags', description: 'Group requests by OpenAPI tags', testId: 'grouping-option-tags' },
{ value: 'path', label: 'Paths', description: 'Group requests by URL path structure', testId: 'grouping-option-path' }
];
const ImportSettings = ({
groupingType,
setGroupingType,
onClose,
onConfirm
}) => {
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => {
dropdownTippyRef.current = ref;
};
const GroupingDropdownIcon = forwardRef((props, ref) => {
const selectedOption = groupingOptions.find((option) => option.value === groupingType);
return (
<div
ref={ref}
className="flex items-center justify-between w-full current-group"
data-testid="grouping-dropdown"
>
<div>
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">{selectedOption.label}</div>
</div>
<IconCaretDown size={16} className="text-gray-400 ml-[0.25rem]" fill="currentColor" />
</div>
);
});
return (
<Portal>
<Modal
size="sm"
title="OpenAPI Import Settings"
handleCancel={onClose}
handleConfirm={onConfirm}
confirmText="Import"
dataTestId="import-settings-modal"
>
<StyledWrapper>
<div className="flex items-center">
<div>
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">Folder arrangement</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
Select whether to create folders according to the spec's paths or tags.
</p>
</div>
<div className="relative">
<Dropdown onCreate={onDropdownCreate} icon={<GroupingDropdownIcon />} placement="bottom-start">
{groupingOptions.map((option) => (
<div
key={option.value}
className="dropdown-item"
data-testid={option.testId}
onClick={() => {
dropdownTippyRef?.current?.hide();
setGroupingType(option.value);
}}
>
{option.label}
</div>
))}
</Dropdown>
</div>
</div>
</StyledWrapper>
</Modal>
</Portal>
);
};
export default ImportSettings;

View File

@@ -15,24 +15,24 @@ import { multiLineMsg } from "utils/common";
import { formatIpcError } from "utils/common/error";
const TitleBar = () => {
const [importedCollection, setImportedCollection] = useState(null);
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false);
const [importData, setImportData] = useState(null);
const dispatch = useDispatch();
const { ipcRenderer } = window;
const handleImportCollection = ({ collection }) => {
setImportedCollection(collection);
const handleImportCollection = ({ rawData, type }) => {
setImportData({ rawData, type });
setImportCollectionModalOpen(false);
setImportCollectionLocationModalOpen(true);
};
const handleImportCollectionLocation = (collectionLocation) => {
dispatch(importCollection(importedCollection, collectionLocation))
const handleImportCollectionLocation = (convertedCollection, collectionLocation) => {
dispatch(importCollection(convertedCollection, collectionLocation))
.then(() => {
setImportCollectionLocationModalOpen(false);
setImportedCollection(null);
setImportData(null);
toast.success('Collection imported successfully');
})
.catch((err) => {
@@ -72,9 +72,10 @@ const TitleBar = () => {
{importCollectionModalOpen ? (
<ImportCollection onClose={() => setImportCollectionModalOpen(false)} handleSubmit={handleImportCollection} />
) : null}
{importCollectionLocationModalOpen ? (
{importCollectionLocationModalOpen && importData ? (
<ImportCollectionLocation
collectionName={importedCollection.name}
rawData={importData.rawData}
format={importData.type}
onClose={() => setImportCollectionLocationModalOpen(false)}
handleSubmit={handleImportCollectionLocation}
/>

View File

@@ -17,7 +17,7 @@ const Welcome = () => {
const { t } = useTranslation();
const sidebarCollapsed = useSelector((state) => state.app.sidebarCollapsed);
const collections = useSelector((state) => state.collections.collections);
const [importedCollection, setImportedCollection] = useState(null);
const [importData, setImportData] = useState(null);
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false);
@@ -30,17 +30,17 @@ const Welcome = () => {
});
};
const handleImportCollection = ({ collection }) => {
setImportedCollection(collection);
const handleImportCollection = ({ rawData, type }) => {
setImportData({ rawData, type });
setImportCollectionModalOpen(false);
setImportCollectionLocationModalOpen(true);
};
const handleImportCollectionLocation = (collectionLocation) => {
dispatch(importCollection(importedCollection, collectionLocation))
const handleImportCollectionLocation = (convertedCollection, collectionLocation) => {
dispatch(importCollection(convertedCollection, collectionLocation))
.then(() => {
setImportCollectionLocationModalOpen(false);
setImportedCollection(null);
setImportData(null);
toast.success(t('WELCOME.COLLECTION_IMPORT_SUCCESS'));
})
.catch((err) => {
@@ -56,9 +56,10 @@ const Welcome = () => {
{importCollectionModalOpen ? (
<ImportCollection onClose={() => setImportCollectionModalOpen(false)} handleSubmit={handleImportCollection} />
) : null}
{importCollectionLocationModalOpen ? (
{importCollectionLocationModalOpen && importData ? (
<ImportCollectionLocation
collectionName={importedCollection.name}
rawData={importData.rawData}
format={importData.type}
onClose={() => setImportCollectionLocationModalOpen(false)}
handleSubmit={handleImportCollectionLocation}
/>

View File

@@ -14,3 +14,27 @@ export const processBrunoCollection = async (jsonData) => {
throw new BrunoError('Import collection failed');
}
};
export const isBrunoCollection = (data) => {
// Check for Bruno collection format
if (typeof data !== 'object' || data === null) {
return false;
}
// Must have a version field that is a non-empty string
if (typeof data.version !== 'string' || !data.version.trim()) {
return false;
}
// Must have a name field that is a non-empty string
if (typeof data.name !== 'string' || !data.name.trim()) {
return false;
}
// Must have an items array
if (!Array.isArray(data.items)) {
return false;
}
return true;
};

View File

@@ -29,13 +29,19 @@ const isPostmanCollection = (data) => {
}
const schema = info.schema;
// Accept schemas hosted at schema.getpostman.com or schema.postman.com
const schemaRegex = /^https:\/\/schema\.(?:getpostman|postman)\.com\//;
if (typeof schema === 'string' && schemaRegex.test(schema)) {
return true;
if (typeof schema !== 'string') {
return false;
}
return false;
// Only accept supported Postman v2.0 and v2.1 schemas
const supportedSchemas = [
'https://schema.getpostman.com/json/collection/v2.0.0/collection.json',
'https://schema.getpostman.com/json/collection/v2.1.0/collection.json',
'https://schema.postman.com/json/collection/v2.0.0/collection.json',
'https://schema.postman.com/json/collection/v2.1.0/collection.json'
];
return supportedSchemas.includes(schema);
};
export { postmanToBruno, readFile, isPostmanCollection };

View File

@@ -18,7 +18,7 @@ test.describe('Import Bruno Collection - Missing Required Schema Fields', () =>
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
// Check for schema validation error messages
const hasImportError = await page.getByText('Import collection failed').first().isVisible();
const hasImportError = await page.getByText('Unsupported collection format').first().isVisible();
expect(hasImportError).toBe(true);

View File

@@ -1,5 +1,6 @@
import { test, expect } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
test.describe('Import Bruno Testbench Collection', () => {
test.beforeAll(async ({ page }) => {
@@ -7,7 +8,12 @@ test.describe('Import Bruno Testbench Collection', () => {
await page.locator('.bruno-logo').click();
});
test('Import Bruno Testbench collection successfully', async ({ page }) => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import Bruno Testbench collection successfully', async ({ page, createTmpDir }) => {
const brunoFile = path.resolve(__dirname, 'fixtures', 'bruno-testbench.json');
await page.getByRole('button', { name: 'Import Collection' }).click();
@@ -29,7 +35,9 @@ test.describe('Import Bruno Testbench Collection', () => {
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('bruno-testbench')).toBeVisible();
// Cleanup: close any open modals
await page.locator('[data-test-id="modal-close-button"]').click();
await page.locator('#collection-location').fill(await createTmpDir('bruno-testbench-test'));
await page.getByRole('button', { name: 'Import', exact: true }).click();
await expect(page.locator('#sidebar-collection-name').getByText('bruno-testbench')).toBeVisible();
});
});

View File

@@ -1,8 +1,14 @@
import { test, expect } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
test.describe('Import Insomnia Collection v4', () => {
test('Import Insomnia Collection v4 successfully', async ({ page }) => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import Insomnia Collection v4 successfully', async ({ page, createTmpDir }) => {
const insomniaFile = path.resolve(__dirname, 'fixtures', 'insomnia-v4.json');
await page.getByRole('button', { name: 'Import Collection' }).click();
@@ -21,10 +27,9 @@ test.describe('Import Insomnia Collection v4', () => {
const locationModal = page.getByRole('dialog');
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('Test API Collection v4')).toBeVisible();
await page.locator('#collection-location').fill(await createTmpDir('insomnia-v4-test'));
await page.getByRole('button', { name: 'Import', exact: true }).click();
// Cleanup: close any open modals
await page.locator('[data-test-id="modal-close-button"]').click();
await expect(page.locator('#sidebar-collection-name').getByText('Test API Collection v4')).toBeVisible();
});
});

View File

@@ -1,8 +1,14 @@
import { test, expect } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
test.describe('Import Insomnia Collection v5', () => {
test('Import Insomnia Collection v5 successfully', async ({ page }) => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import Insomnia Collection v5 successfully', async ({ page, createTmpDir }) => {
const insomniaFile = path.resolve(__dirname, 'fixtures', 'insomnia-v5.yaml');
await page.getByRole('button', { name: 'Import Collection' }).click();
@@ -21,10 +27,9 @@ test.describe('Import Insomnia Collection v5', () => {
const locationModal = page.getByRole('dialog');
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('Test API Collection v5')).toBeVisible();
await page.locator('#collection-location').fill(await createTmpDir('insomnia-v5-test'));
await page.getByRole('button', { name: 'Import', exact: true }).click();
// Cleanup: close any open modals
await page.locator('[data-test-id="modal-close-button"]').click();
await expect(page.locator('#sidebar-collection-name').getByText('Test API Collection v5')).toBeVisible();
});
});

View File

@@ -18,7 +18,7 @@ test.describe('Invalid Insomnia Collection - Missing Collection Array', () => {
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
// Check for error message
const hasError = await page.getByText('Import collection failed').first().isVisible();
const hasError = await page.getByText('Unsupported collection format').first().isVisible();
expect(hasError).toBe(true);
// Cleanup: close any open modals

View File

@@ -24,18 +24,6 @@ test.describe('OpenAPI Duplicate Names Handling', () => {
// wait for the file processing to complete
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
// verify that the import settings modal appears
const settingsModal = page.getByTestId('import-settings-modal');
await expect(settingsModal.locator('.bruno-modal-header-title')).toContainText('OpenAPI Import Settings');
// click the Import button in the settings modal footer
await settingsModal.getByRole('button', { name: 'Import' }).click();
// verify that the collection location modal appears (OpenAPI files go directly to location modal)
const locationModal = page.getByTestId('import-collection-location-modal');
// verify the collection name is correctly parsed despite duplicate operation names
await expect(locationModal.getByText('Duplicate Test Collection')).toBeVisible();
// select a location
await page.locator('#collection-location').fill(await createTmpDir('duplicate-test'));
await page.getByRole('button', { name: 'Import', exact: true }).click();

View File

@@ -1,8 +1,14 @@
import { test, expect } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
test.describe('Import OpenAPI v3 JSON Collection', () => {
test('Import simple OpenAPI v3 JSON successfully', async ({ page }) => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import simple OpenAPI v3 JSON successfully', async ({ page, createTmpDir }) => {
const openApiFile = path.resolve(__dirname, 'fixtures', 'openapi-simple.json');
await page.getByRole('button', { name: 'Import Collection' }).click();
@@ -17,13 +23,6 @@ test.describe('Import OpenAPI v3 JSON Collection', () => {
// Wait for the loader to disappear
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
// verify that the import settings modal appears
const settingsModal = page.getByTestId('import-settings-modal');
await expect(settingsModal.locator('.bruno-modal-header-title')).toContainText('OpenAPI Import Settings');
// click the Import button in the settings modal footer
await settingsModal.getByRole('button', { name: 'Import' }).click();
// Verify that the Import Collection modal is displayed (for location selection)
const locationModal = page.getByRole('dialog');
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
@@ -31,7 +30,11 @@ test.describe('Import OpenAPI v3 JSON Collection', () => {
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('Simple Test API')).toBeVisible();
// Cleanup: close any open modals
await page.locator('[data-test-id="modal-close-button"]').click();
// Select a location and import
await page.locator('#collection-location').fill(await createTmpDir('simple-test'));
await page.getByRole('button', { name: 'Import', exact: true }).click();
// Verify the collection was imported successfully
await expect(page.locator('#sidebar-collection-name').getByText('Simple Test API')).toBeVisible();
});
});

View File

@@ -1,8 +1,14 @@
import { test, expect } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
test.describe('Import OpenAPI v3 YAML Collection', () => {
test('Import comprehensive OpenAPI v3 YAML successfully', async ({ page }) => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import comprehensive OpenAPI v3 YAML successfully', async ({ page, createTmpDir }) => {
const openApiFile = path.resolve(__dirname, 'fixtures', 'openapi-comprehensive.yaml');
await page.getByRole('button', { name: 'Import Collection' }).click();
@@ -14,13 +20,6 @@ test.describe('Import OpenAPI v3 YAML Collection', () => {
await page.setInputFiles('input[type="file"]', openApiFile);
// verify that the import settings modal appears
const settingsModal = page.getByTestId('import-settings-modal');
await expect(settingsModal.locator('.bruno-modal-header-title')).toContainText('OpenAPI Import Settings');
// click the Import button in the settings modal footer
await settingsModal.getByRole('button', { name: 'Import' }).click();
// Wait for the loader to disappear
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
@@ -31,7 +30,11 @@ test.describe('Import OpenAPI v3 YAML Collection', () => {
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('Comprehensive API Test Collection')).toBeVisible();
// Cleanup: close any open modals
await page.locator('[data-test-id="modal-close-button"]').click();
// Select a location and import
await page.locator('#collection-location').fill(await createTmpDir('comprehensive-test'));
await page.getByRole('button', { name: 'Import', exact: true }).click();
// Verify the collection was imported successfully
await expect(page.locator('#sidebar-collection-name').getByText('Comprehensive API Test Collection')).toBeVisible();
});
});

View File

@@ -18,7 +18,7 @@ test.describe('Invalid OpenAPI - Missing Info Section', () => {
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
// The OpenAPI parser might handle missing info gracefully with defaults
const hasError = await page.getByText('Import collection failed').first().isVisible();
const hasError = await page.getByText('Unsupported collection format').first().isVisible();
// Either should show an error or create an "Untitled Collection"
expect(hasError).toBe(true);

View File

@@ -21,16 +21,6 @@ test.describe('OpenAPI Newline Handling', () => {
// upload the OpenAPI file with problematic operation names
await page.setInputFiles('input[type="file"]', openApiFile);
// verify that the import settings modal appears
const settingsModal = page.getByTestId('import-settings-modal');
await expect(settingsModal.locator('.bruno-modal-header-title')).toContainText('OpenAPI Import Settings');
// click the Import button in the settings modal footer
await settingsModal.getByRole('button', { name: 'Import' }).click();
// wait for the file processing to complete
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
// verify that the collection location modal appears (OpenAPI files go directly to location modal)
const locationModal = page.getByTestId('import-collection-location-modal');
await expect(locationModal.getByText('Newline Test Collection')).toBeVisible();

View File

@@ -24,24 +24,14 @@ test.describe('OpenAPI Path-Based Grouping', () => {
// Wait for the loader to disappear
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
// Verify that the import settings modal appears
const settingsModal = page.getByTestId('import-settings-modal');
await expect(settingsModal.locator('.bruno-modal-header-title')).toContainText('OpenAPI Import Settings');
// Select path-based grouping from the dropdown
await settingsModal.getByTestId('grouping-dropdown').click();
// Wait for dropdown options to be visible (they might be rendered outside the modal)
await page.getByTestId('grouping-option-path').waitFor({ state: 'visible' });
await page.getByTestId('grouping-option-path').click();
// Now import the collection with path-based grouping
await settingsModal.getByRole('button', { name: 'Import' }).click();
// Verify that the collection location modal appears
const locationModal = page.getByTestId('import-collection-location-modal');
await expect(locationModal.getByText('Path Grouping Test API')).toBeVisible();
// Select path-based grouping from dropdown
await page.getByTestId('grouping-dropdown').click();
await page.getByTestId('grouping-option-path').click();
// Select a location and import
await page.locator('#collection-location').fill(await createTmpDir('path-grouping-test'));
await page.getByRole('button', { name: 'Import', exact: true }).click();

View File

@@ -1,8 +1,14 @@
import { test, expect } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
test.describe('Import Postman Collection v2.0', () => {
test('Import Postman Collection v2.0 successfully', async ({ page }) => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import Postman Collection v2.0 successfully', async ({ page, createTmpDir }) => {
const postmanFile = path.resolve(__dirname, 'fixtures', 'postman-v20.json');
await page.getByRole('button', { name: 'Import Collection' }).click();
@@ -24,7 +30,11 @@ test.describe('Import Postman Collection v2.0', () => {
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('Postman v2.0 Collection')).toBeVisible();
// Cleanup: close any open modals
await page.locator('[data-test-id="modal-close-button"]').click();
// Select a location and import
await page.locator('#collection-location').fill(await createTmpDir('postman-v20-test'));
await page.getByRole('button', { name: 'Import', exact: true }).click();
// Verify the collection was imported successfully
await expect(page.locator('#sidebar-collection-name').getByText('Postman v2.0 Collection')).toBeVisible();
});
});

View File

@@ -1,8 +1,14 @@
import { test, expect } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
test.describe('Import Postman Collection v2.1', () => {
test('Import Postman Collection v2.1 successfully', async ({ page }) => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Import Postman Collection v2.1 successfully', async ({ page, createTmpDir }) => {
const postmanFile = path.resolve(__dirname, 'fixtures', 'postman-v21.json');
await page.getByRole('button', { name: 'Import Collection' }).click();
@@ -24,7 +30,11 @@ test.describe('Import Postman Collection v2.1', () => {
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('Postman v2.1 Collection')).toBeVisible();
// Cleanup: close any open modals
await page.locator('[data-test-id="modal-close-button"]').click();
// Select a location and import
await page.locator('#collection-location').fill(await createTmpDir('postman-v21-test'));
await page.getByRole('button', { name: 'Import', exact: true }).click();
// Verify the collection was imported successfully
await expect(page.locator('#sidebar-collection-name').getByText('Postman v2.1 Collection')).toBeVisible();
});
});

View File

@@ -18,7 +18,8 @@ test.describe('Invalid Postman Collection - Invalid JSON', () => {
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
// Check for error message
const hasError = await page.getByText('Conversion failed').first().isVisible();
const hasError = await page.getByText('Unsupported collection format').first().isVisible();
expect(hasError).toBe(true);
// Cleanup: close any open modals

View File

@@ -18,7 +18,7 @@ test.describe('Invalid Postman Collection - Missing Info', () => {
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
// Check for error message
const hasError = await page.getByText('Import collection failed').first().isVisible();
const hasError = await page.getByText('Unsupported collection format').first().isVisible();
expect(hasError).toBe(true);
// Cleanup: close any open modals

View File

@@ -18,7 +18,7 @@ test.describe('Invalid Postman Collection - Invalid Schema', () => {
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
// Check for error message
const hasError = await page.getByText('Conversion failed').first().isVisible();
const hasError = await page.getByText('Unsupported collection format').first().isVisible();
expect(hasError).toBe(true);
// Cleanup: close any open modals

View File

@@ -18,7 +18,7 @@ test.describe('Invalid Postman Collection - Malformed Structure', () => {
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
// Check for error message
const hasError = await page.getByText('Import collection failed').first().isVisible();
const hasError = await page.getByText('Unsupported collection format').first().isVisible();
expect(hasError).toBe(true);
// Cleanup: close any open modals

View File

@@ -32,12 +32,10 @@ test.describe('Import WSDL Collection', () => {
const locationModal = page.getByRole('dialog');
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
// Wait for collection to appear in the location modal
await expect(locationModal.getByText('TestWSDLServiceXML')).toBeVisible();
// select a location
await page.locator('#collection-location').fill(await createTmpDir('wsdl-xml-test'));
await page.getByRole('button', { name: 'Import', exact: true }).click();
await expect(page.locator('#sidebar-collection-name').getByText('TestWSDLServiceXML')).toBeVisible();
});
await test.step('Verify that the collection was imported successfully', async () => {