mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-25 13:45:52 +00:00
revamp: collection and global env selector dropdown (#5542)
* revamp: collection and global env selector dropdown --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Pragadesh-45 <54320162+Pragadesh-45@users.noreply.github.com> Co-authored-by: Anoop M D <anoop.md1421@gmail.com> Co-authored-by: sanish-bruno <sanish@usebruno.com> Co-authored-by: bernborgess <bernborgesse@outlook.com> Co-authored-by: lohit <lohit@usebruno.com> Co-authored-by: Its-Treason <39559178+Its-treason@users.noreply.github.com> Co-authored-by: jayakrishnancn <jayakrishnancn@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import { IconPlus, IconDownload, IconSettings } from '@tabler/icons';
|
||||
|
||||
const EnvironmentListContent = ({
|
||||
environments,
|
||||
activeEnvironmentUid,
|
||||
description,
|
||||
onEnvironmentSelect,
|
||||
onSettingsClick,
|
||||
onCreateClick,
|
||||
onImportClick
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
{environments && environments.length > 0 ? (
|
||||
<>
|
||||
<div className="environment-list">
|
||||
<div className="dropdown-item no-environment" onClick={() => onEnvironmentSelect(null)}>
|
||||
<span>No Environment</span>
|
||||
</div>
|
||||
<div className="pb-[2.625rem]">
|
||||
{environments.map((env) => (
|
||||
<div
|
||||
key={env.uid}
|
||||
className={`dropdown-item ${env.uid === activeEnvironmentUid ? 'active' : ''}`}
|
||||
onClick={() => onEnvironmentSelect(env)}
|
||||
>
|
||||
<span className="max-w-32 truncate no-wrap">{env.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="dropdown-item configure-button">
|
||||
<button onClick={onSettingsClick} id="configure-env">
|
||||
<IconSettings size={16} strokeWidth={1.5} />
|
||||
<span>Configure</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="empty-state">
|
||||
<h3>Ready to get started?</h3>
|
||||
<p>{description}</p>
|
||||
<div className="space-y-2">
|
||||
<button onClick={onCreateClick} id="create-env">
|
||||
<IconPlus size={16} strokeWidth={1.5} />
|
||||
Create
|
||||
</button>
|
||||
<button onClick={onImportClick} id="import-env">
|
||||
<IconDownload size={16} strokeWidth={1.5} />
|
||||
Import
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvironmentListContent;
|
||||
@@ -2,14 +2,204 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
.current-environment {
|
||||
background-color: ${(props) => props.theme.sidebar.badge.bg};
|
||||
border-radius: 15px;
|
||||
border-radius: 0.9375rem;
|
||||
padding: 0.25rem 0.5rem 0.25rem 0.75rem;
|
||||
user-select: none;
|
||||
background-color: transparent;
|
||||
border: 1px solid ${(props) => props.theme.dropdown.selectedColor};
|
||||
line-height: 1rem;
|
||||
|
||||
.caret {
|
||||
margin-left: 0.25rem;
|
||||
color: rgb(140, 140, 140);
|
||||
fill: rgb(140, 140, 140);
|
||||
}
|
||||
|
||||
.env-icon {
|
||||
margin-right: 0.25rem;
|
||||
color: ${(props) => props.theme.dropdown.selectedColor};
|
||||
}
|
||||
|
||||
.env-text {
|
||||
color: ${(props) => props.theme.dropdown.selectedColor};
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.env-separator {
|
||||
color: #8c8c8c;
|
||||
margin: 0 0.25rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.env-text-inactive {
|
||||
color: ${(props) => props.theme.dropdown.color};
|
||||
font-size: 0.875rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.no-environments {
|
||||
background-color: ${(props) => props.theme.sidebar.badge.bg};
|
||||
border: 1px solid transparent;
|
||||
color: ${(props) => props.theme.dropdown.secondaryText};
|
||||
}
|
||||
}
|
||||
|
||||
.tippy-box {
|
||||
min-width: 11.875rem;
|
||||
min-height: 15.0625rem;
|
||||
font-size: 0.8125rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tippy-box .tippy-content {
|
||||
padding: 0;
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.35rem 0.6rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.8125rem;
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: ${(props) => props.theme.dropdown.hoverBg};
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: ${(props) => props.theme.dropdown.selectedBg};
|
||||
color: ${(props) => props.theme.dropdown.selectedColor};
|
||||
}
|
||||
|
||||
&.no-environment {
|
||||
color: ${(props) => props.theme.dropdown.mutedText};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.configure-button {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: ${(props) => props.theme.dropdown.bg};
|
||||
border-top: 0.0625rem solid ${(props) => props.theme.dropdown.separator};
|
||||
z-index: 10;
|
||||
margin: 0;
|
||||
|
||||
button {
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
color: var(--color-tab-inactive);
|
||||
font-size: 0.8125rem;
|
||||
|
||||
.tab-content-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.125rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: ${(props) => props.theme.tabs.active.color};
|
||||
border-bottom-color: ${(props) => props.theme.tabs.active.border};
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item-list {
|
||||
max-height: 75vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
max-width: 20rem;
|
||||
margin: 0 auto;
|
||||
padding: 0.35rem 0.6rem;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 12.5rem;
|
||||
|
||||
h3 {
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
p {
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
opacity: 0.75;
|
||||
font-size: 0.6875rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 1rem;
|
||||
max-width: 11.875rem;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.space-y-2 {
|
||||
width: 100%;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.space-y-2 > button {
|
||||
border: 0.0625rem solid ${(props) => props.theme.dropdown.primaryText};
|
||||
background: transparent;
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.dropdown.hoverBg};
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-collection-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 1rem;
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
opacity: 0.75;
|
||||
|
||||
svg {
|
||||
margin: 0 auto 1rem auto;
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,95 +1,240 @@
|
||||
import React, { useRef, forwardRef, useState } from 'react';
|
||||
import React, { useState, useRef, forwardRef } from 'react';
|
||||
import find from 'lodash/find';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { IconWorld, IconDatabase, IconCaretDown, IconSettings, IconPlus, IconDownload } from '@tabler/icons';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { updateEnvironmentSettingsModalVisibility } from 'providers/ReduxStore/slices/app';
|
||||
import { IconSettings, IconCaretDown, IconDatabase, IconDatabaseOff } from '@tabler/icons';
|
||||
import EnvironmentSettings from '../EnvironmentSettings';
|
||||
import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { selectGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import EnvironmentListContent from './EnvironmentListContent/index';
|
||||
import EnvironmentSettings from '../EnvironmentSettings';
|
||||
import GlobalEnvironmentSettings from 'components/GlobalEnvironments/EnvironmentSettings';
|
||||
import CreateEnvironment from '../EnvironmentSettings/CreateEnvironment';
|
||||
import ImportEnvironment from '../EnvironmentSettings/ImportEnvironment';
|
||||
import CreateGlobalEnvironment from 'components/GlobalEnvironments/EnvironmentSettings/CreateEnvironment';
|
||||
import ImportGlobalEnvironment from 'components/GlobalEnvironments/EnvironmentSettings/ImportEnvironment';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const EnvironmentSelector = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const dropdownTippyRef = useRef();
|
||||
const [openSettingsModal, setOpenSettingsModal] = useState(false);
|
||||
const { environments, activeEnvironmentUid } = collection;
|
||||
const activeEnvironment = activeEnvironmentUid ? find(environments, (e) => e.uid === activeEnvironmentUid) : null;
|
||||
const [activeTab, setActiveTab] = useState('collection');
|
||||
const [showGlobalSettings, setShowGlobalSettings] = useState(false);
|
||||
const [showCollectionSettings, setShowCollectionSettings] = useState(false);
|
||||
const [showCreateGlobalModal, setShowCreateGlobalModal] = useState(false);
|
||||
const [showImportGlobalModal, setShowImportGlobalModal] = useState(false);
|
||||
const [showCreateCollectionModal, setShowCreateCollectionModal] = useState(false);
|
||||
const [showImportCollectionModal, setShowImportCollectionModal] = useState(false);
|
||||
|
||||
const globalEnvironments = useSelector((state) => state.globalEnvironments.globalEnvironments);
|
||||
const activeGlobalEnvironmentUid = useSelector((state) => state.globalEnvironments.activeGlobalEnvironmentUid);
|
||||
const activeGlobalEnvironment = activeGlobalEnvironmentUid
|
||||
? find(globalEnvironments, (e) => e.uid === activeGlobalEnvironmentUid)
|
||||
: null;
|
||||
|
||||
const environments = collection?.environments || [];
|
||||
const activeEnvironmentUid = collection?.activeEnvironmentUid;
|
||||
const activeCollectionEnvironment = activeEnvironmentUid
|
||||
? find(environments, (e) => e.uid === activeEnvironmentUid)
|
||||
: null;
|
||||
|
||||
const tabs = [
|
||||
{ id: 'collection', label: 'Collection', icon: <IconDatabase size={16} strokeWidth={1.5} /> },
|
||||
{ id: 'global', label: 'Global', icon: <IconWorld size={16} strokeWidth={1.5} /> }
|
||||
];
|
||||
|
||||
const onDropdownCreate = (ref) => {
|
||||
dropdownTippyRef.current = ref;
|
||||
};
|
||||
|
||||
// Get description based on active tab
|
||||
const description =
|
||||
activeTab === 'collection'
|
||||
? 'Create your first environment to begin working with your collection.'
|
||||
: 'Create your first global environment to begin working across collections.';
|
||||
|
||||
// Environment selection handler
|
||||
const handleEnvironmentSelect = (environment) => {
|
||||
const action =
|
||||
activeTab === 'collection'
|
||||
? selectEnvironment(environment ? environment.uid : null, collection.uid)
|
||||
: selectGlobalEnvironment({ environmentUid: environment ? environment.uid : null });
|
||||
|
||||
dispatch(action)
|
||||
.then(() => {
|
||||
if (environment) {
|
||||
toast.success(`Environment changed to ${environment.name}`);
|
||||
} else {
|
||||
toast.success('No Environments are active now');
|
||||
}
|
||||
dropdownTippyRef.current.hide();
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error('An error occurred while selecting the environment');
|
||||
});
|
||||
};
|
||||
|
||||
// Settings handler
|
||||
const handleSettingsClick = () => {
|
||||
if (activeTab === 'collection') {
|
||||
dispatch(updateEnvironmentSettingsModalVisibility(true));
|
||||
setShowCollectionSettings(true);
|
||||
} else {
|
||||
setShowGlobalSettings(true);
|
||||
}
|
||||
dropdownTippyRef.current.hide();
|
||||
};
|
||||
|
||||
// Create handler
|
||||
const handleCreateClick = () => {
|
||||
if (activeTab === 'collection') {
|
||||
setShowCreateCollectionModal(true);
|
||||
} else {
|
||||
setShowCreateGlobalModal(true);
|
||||
}
|
||||
dropdownTippyRef.current.hide();
|
||||
};
|
||||
|
||||
// Import handler
|
||||
const handleImportClick = () => {
|
||||
if (activeTab === 'collection') {
|
||||
setShowImportCollectionModal(true);
|
||||
} else {
|
||||
setShowImportGlobalModal(true);
|
||||
}
|
||||
dropdownTippyRef.current.hide();
|
||||
};
|
||||
|
||||
// Modal handlers
|
||||
const handleCloseSettings = () => {
|
||||
setShowGlobalSettings(false);
|
||||
setShowCollectionSettings(false);
|
||||
dispatch(updateEnvironmentSettingsModalVisibility(false));
|
||||
};
|
||||
|
||||
// Create icon component for dropdown trigger
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
const hasAnyEnv = activeGlobalEnvironment || activeCollectionEnvironment;
|
||||
|
||||
const displayContent = hasAnyEnv ? (
|
||||
<>
|
||||
{activeCollectionEnvironment && (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<IconDatabase size={14} strokeWidth={1.5} className="env-icon" />
|
||||
<span className="env-text max-w-24 truncate no-wrap">{activeCollectionEnvironment.name}</span>
|
||||
</div>
|
||||
{activeGlobalEnvironment && <span className="env-separator">|</span>}
|
||||
</>
|
||||
)}
|
||||
{activeGlobalEnvironment && (
|
||||
<div className="flex items-center">
|
||||
<IconWorld size={14} strokeWidth={1.5} className="env-icon" />
|
||||
<span className="env-text max-w-24 truncate no-wrap">{activeGlobalEnvironment.name}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<span className="env-text-inactive">No environments</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={ref} className="current-environment collection-environment flex items-center justify-center pl-3 pr-2 py-1 select-none">
|
||||
<p className="text-nowrap truncate max-w-32" title={activeEnvironment ? activeEnvironment.name : 'No Environment'}>{activeEnvironment ? activeEnvironment.name : 'No Environment'}</p>
|
||||
<div
|
||||
ref={ref}
|
||||
className={`current-environment flex align-center justify-center cursor-pointer bg-transparent ${
|
||||
!hasAnyEnv ? 'no-environments' : ''
|
||||
}`}
|
||||
data-testid="environment-selector-trigger"
|
||||
>
|
||||
{displayContent}
|
||||
<IconCaretDown className="caret" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const handleSettingsIconClick = () => {
|
||||
setOpenSettingsModal(true);
|
||||
dispatch(updateEnvironmentSettingsModalVisibility(true));
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setOpenSettingsModal(false);
|
||||
dispatch(updateEnvironmentSettingsModalVisibility(false));
|
||||
};
|
||||
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const onSelect = (environment) => {
|
||||
dispatch(selectEnvironment(environment ? environment.uid : null, collection.uid))
|
||||
.then(() => {
|
||||
if (environment) {
|
||||
toast.success(`Environment changed to ${environment.name}`);
|
||||
} else {
|
||||
toast.success(`No Environments are active now`);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.log(err) && toast.error('An error occurred while selecting the environment'));
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex items-center cursor-pointer environment-selector">
|
||||
<div className="environment-selector flex align-center cursor-pointer">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<div className="label-item font-medium">Collection Environments</div>
|
||||
{environments && environments.length
|
||||
? environments.map((e) => (
|
||||
<div
|
||||
className={`dropdown-item ${e?.uid === activeEnvironmentUid ? 'active' : ''}`}
|
||||
key={e.uid}
|
||||
onClick={() => {
|
||||
onSelect(e);
|
||||
dropdownTippyRef.current.hide();
|
||||
}}
|
||||
>
|
||||
<IconDatabase size={18} strokeWidth={1.5} /> <span className="ml-2 break-all">{e.name}</span>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onSelect(null);
|
||||
}}
|
||||
>
|
||||
<IconDatabaseOff size={18} strokeWidth={1.5} />
|
||||
<span className="ml-2">No Environment</span>
|
||||
{/* Tab Headers */}
|
||||
<div className="tab-header flex justify-center p-[0.75rem]">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
className={`tab-button whitespace-nowrap pb-[0.375rem] border-b-[0.125rem] bg-transparent flex align-center cursor-pointer transition-all duration-200 mr-[1.25rem] ${
|
||||
activeTab === tab.id ? 'active' : 'inactive'
|
||||
}`}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
data-testid={`env-tab-${tab.id}`}
|
||||
>
|
||||
<span className="tab-content-wrapper">
|
||||
{tab.icon}
|
||||
{tab.label}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="dropdown-item border-top" onClick={() => {
|
||||
handleSettingsIconClick();
|
||||
dropdownTippyRef.current.hide();
|
||||
}}>
|
||||
<div className="pr-2 text-gray-600" id="Configure">
|
||||
<IconSettings size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span>Configure</span>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="tab-content">
|
||||
<EnvironmentListContent
|
||||
environments={activeTab === 'collection' ? environments : globalEnvironments}
|
||||
activeEnvironmentUid={activeTab === 'collection' ? activeEnvironmentUid : activeGlobalEnvironmentUid}
|
||||
description={description}
|
||||
onEnvironmentSelect={handleEnvironmentSelect}
|
||||
onSettingsClick={handleSettingsClick}
|
||||
onCreateClick={handleCreateClick}
|
||||
onImportClick={handleImportClick}
|
||||
/>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{openSettingsModal && <EnvironmentSettings collection={collection} onClose={handleModalClose} />}
|
||||
|
||||
{/* Modals - Rendered outside dropdown to avoid conflicts */}
|
||||
{showGlobalSettings && (
|
||||
<GlobalEnvironmentSettings globalEnvironments={globalEnvironments} onClose={handleCloseSettings} />
|
||||
)}
|
||||
|
||||
{showCollectionSettings && <EnvironmentSettings collection={collection} onClose={handleCloseSettings} />}
|
||||
|
||||
{showCreateGlobalModal && (
|
||||
<CreateGlobalEnvironment
|
||||
onClose={() => setShowCreateGlobalModal(false)}
|
||||
onEnvironmentCreated={() => {
|
||||
setShowGlobalSettings(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showImportGlobalModal && (
|
||||
<ImportGlobalEnvironment
|
||||
onClose={() => setShowImportGlobalModal(false)}
|
||||
onEnvironmentCreated={() => {
|
||||
setShowGlobalSettings(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showCreateCollectionModal && (
|
||||
<CreateEnvironment
|
||||
collection={collection}
|
||||
onClose={() => setShowCreateCollectionModal(false)}
|
||||
onEnvironmentCreated={() => {
|
||||
setShowCollectionSettings(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showImportCollectionModal && (
|
||||
<ImportEnvironment
|
||||
collection={collection}
|
||||
onClose={() => setShowImportCollectionModal(false)}
|
||||
onEnvironmentCreated={() => {
|
||||
setShowCollectionSettings(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import Portal from 'components/Portal';
|
||||
import Modal from 'components/Modal';
|
||||
import { validateName, validateNameError } from 'utils/common/regex';
|
||||
|
||||
const CreateEnvironment = ({ collection, onClose }) => {
|
||||
const CreateEnvironment = ({ collection, onClose, onEnvironmentCreated }) => {
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
|
||||
@@ -37,6 +37,10 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
.then(() => {
|
||||
toast.success('Environment created in collection');
|
||||
onClose();
|
||||
// Call the callback if provided
|
||||
if (onEnvironmentCreated) {
|
||||
onEnvironmentCreated();
|
||||
}
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while creating the environment'));
|
||||
}
|
||||
|
||||
@@ -254,6 +254,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
className="btn-add-param text-link pr-2 py-3 mt-2 select-none"
|
||||
onClick={addVariable}
|
||||
id="add-variable"
|
||||
data-testid="add-variable"
|
||||
>
|
||||
+ Add Variable
|
||||
</button>
|
||||
@@ -261,15 +262,15 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary mt-2 flex items-center" onClick={formik.handleSubmit}>
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary mt-2 flex items-center" onClick={formik.handleSubmit} data-testid="save-env">
|
||||
<IconDeviceFloppy size={16} strokeWidth={1.5} className="mr-1" />
|
||||
Save
|
||||
</button>
|
||||
<button type="submit" className="ml-2 px-1 submit btn btn-sm btn-close mt-2 flex items-center" onClick={handleReset}>
|
||||
<button type="submit" className="ml-2 px-1 submit btn btn-sm btn-close mt-2 flex items-center" onClick={handleReset} data-testid="reset-env">
|
||||
<IconRefresh size={16} strokeWidth={1.5} className="mr-1" />
|
||||
Reset
|
||||
</button>
|
||||
<button type="submit" className="submit btn btn-sm btn-close mt-2 flex items-center" onClick={onActivate}>
|
||||
<button type="submit" className="submit btn btn-sm btn-close mt-2 flex items-center" onClick={onActivate} data-testid="activate-env">
|
||||
<IconCircleCheck size={16} strokeWidth={1.5} className="mr-1" />
|
||||
Activate
|
||||
</button>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { importEnvironment } from 'providers/ReduxStore/slices/collections/actio
|
||||
import { toastError } from 'utils/common/error';
|
||||
import { IconDatabaseImport } from '@tabler/icons';
|
||||
|
||||
const ImportEnvironment = ({ collection, onClose }) => {
|
||||
const ImportEnvironment = ({ collection, onClose, onEnvironmentCreated }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleImportPostmanEnvironment = () => {
|
||||
@@ -36,17 +36,22 @@ const ImportEnvironment = ({ collection, onClose }) => {
|
||||
})
|
||||
.then(() => {
|
||||
onClose();
|
||||
// Call the callback if provided
|
||||
if (onEnvironmentCreated) {
|
||||
onEnvironmentCreated();
|
||||
}
|
||||
})
|
||||
.catch((err) => toastError(err, 'Postman Import environment failed'));
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal size="sm" title="Import Environment" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
|
||||
<Modal size="sm" title="Import Environment" hideFooter={true} handleConfirm={onClose} handleCancel={onClose} dataTestId="import-environment-modal">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleImportPostmanEnvironment}
|
||||
className="flex justify-center flex-col items-center w-full dark:bg-zinc-700 rounded-lg border-2 border-dashed border-zinc-300 dark:border-zinc-400 p-12 text-center hover:border-zinc-400 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2"
|
||||
data-testid="import-postman-environment"
|
||||
>
|
||||
<IconDatabaseImport size={64} />
|
||||
<span className="mt-2 block text-sm font-semibold">Import your Postman environments</span>
|
||||
|
||||
@@ -56,9 +56,8 @@ const EnvironmentSettings = ({ collection, onClose }) => {
|
||||
) : tab === 'import' ? (
|
||||
<ImportEnvironment collection={collection} onClose={() => setTab('default')} />
|
||||
) : (
|
||||
<></>
|
||||
<DefaultTab setTab={setTab} />
|
||||
)}
|
||||
<DefaultTab setTab={setTab} />
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
.current-environment {
|
||||
}
|
||||
.environment-active {
|
||||
padding: 0.3rem 0.4rem;
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
border: solid 1px ${(props) => props.theme.colors.text.yellow} !important;
|
||||
}
|
||||
.environment-selector {
|
||||
.active: {
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -1,100 +0,0 @@
|
||||
import React, { useRef, forwardRef, useState } from 'react';
|
||||
import find from 'lodash/find';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { IconSettings, IconWorld, IconDatabase, IconDatabaseOff, IconCheck } from '@tabler/icons';
|
||||
import EnvironmentSettings from '../EnvironmentSettings';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { selectGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||
import ToolHint from 'components/ToolHint/index';
|
||||
|
||||
const EnvironmentSelector = () => {
|
||||
const dispatch = useDispatch();
|
||||
const dropdownTippyRef = useRef();
|
||||
const globalEnvironments = useSelector((state) => state.globalEnvironments.globalEnvironments);
|
||||
const activeGlobalEnvironmentUid = useSelector((state) => state.globalEnvironments.activeGlobalEnvironmentUid);
|
||||
const [openSettingsModal, setOpenSettingsModal] = useState(false);
|
||||
const activeEnvironment = activeGlobalEnvironmentUid ? find(globalEnvironments, (e) => e.uid === activeGlobalEnvironmentUid) : null;
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className={`current-environment global-environment flex flex-row gap-1 rounded-xl text-xs cursor-pointer items-center justify-center select-none ${activeGlobalEnvironmentUid? 'environment-active': ''}`}>
|
||||
<ToolHint text="Global Environments" toolhintId="GlobalEnvironmentsToolhintId" className='flex flex-row'>
|
||||
<IconWorld className="globe" size={16} strokeWidth={1.5} />
|
||||
{
|
||||
activeEnvironment ? <div className='text-nowrap truncate max-w-32'>{activeEnvironment?.name}</div> : null
|
||||
}
|
||||
</ToolHint>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const handleSettingsIconClick = () => {
|
||||
setOpenSettingsModal(true);
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setOpenSettingsModal(false);
|
||||
};
|
||||
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const onSelect = (environment) => {
|
||||
dispatch(selectGlobalEnvironment({ environmentUid: environment ? environment.uid : null }))
|
||||
.then(() => {
|
||||
if (environment) {
|
||||
toast.success(`Environment changed to ${environment.name}`);
|
||||
} else {
|
||||
toast.success(`No Environments are active now`);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.log(err) && toast.error('An error occurred while selecting the environment'));
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex items-center cursor-pointer environment-selector mr-3">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end" transparent={true}>
|
||||
<div className="label-item font-medium">Global Environments</div>
|
||||
{globalEnvironments && globalEnvironments.length
|
||||
? globalEnvironments.map((e) => (
|
||||
<div
|
||||
className={`dropdown-item ${e?.uid === activeGlobalEnvironmentUid ? 'active' : ''}`}
|
||||
key={e.uid}
|
||||
onClick={() => {
|
||||
onSelect(e);
|
||||
dropdownTippyRef.current.hide();
|
||||
}}
|
||||
>
|
||||
<IconDatabase size={18} strokeWidth={1.5} /> <span className="ml-2 break-all">{e.name}</span>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onSelect(null);
|
||||
}}
|
||||
>
|
||||
<IconDatabaseOff size={18} strokeWidth={1.5} />
|
||||
<span className="ml-2">No Environment</span>
|
||||
</div>
|
||||
<div className="dropdown-item border-top" onClick={() => {
|
||||
handleSettingsIconClick();
|
||||
dropdownTippyRef.current.hide();
|
||||
}}>
|
||||
<div className="pr-2 text-gray-600">
|
||||
<IconSettings size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span>Configure</span>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{openSettingsModal && <EnvironmentSettings globalEnvironments={globalEnvironments} activeGlobalEnvironmentUid={activeGlobalEnvironmentUid} onClose={handleModalClose} />}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvironmentSelector;
|
||||
@@ -8,7 +8,7 @@ import Modal from 'components/Modal';
|
||||
import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||
import { validateName, validateNameError } from 'utils/common/regex';
|
||||
|
||||
const CreateEnvironment = ({ onClose }) => {
|
||||
const CreateEnvironment = ({ onClose, onEnvironmentCreated }) => {
|
||||
const globalEnvs = useSelector((state) => state?.globalEnvironments?.globalEnvironments);
|
||||
|
||||
const validateEnvironmentName = (name) => {
|
||||
@@ -39,6 +39,10 @@ const CreateEnvironment = ({ onClose }) => {
|
||||
.then(() => {
|
||||
toast.success('Global environment created!');
|
||||
onClose();
|
||||
// Call the callback if provided
|
||||
if (onEnvironmentCreated) {
|
||||
onEnvironmentCreated();
|
||||
}
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while creating the environment'));
|
||||
}
|
||||
|
||||
@@ -179,6 +179,7 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
|
||||
ref={addButtonRef}
|
||||
className="btn-add-param text-link pr-2 py-3 mt-2 select-none"
|
||||
onClick={addVariable}
|
||||
data-testid="add-variable"
|
||||
>
|
||||
+ Add Variable
|
||||
</button>
|
||||
@@ -186,10 +187,10 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}>
|
||||
<button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit} data-testid="save-env">
|
||||
Save
|
||||
</button>
|
||||
<button type="submit" className="ml-2 px-1 submit btn btn-md btn-secondary mt-2" onClick={handleReset}>
|
||||
<button type="submit" className="ml-2 px-1 submit btn btn-md btn-secondary mt-2" onClick={handleReset} data-testid="reset-env">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { IconDatabaseImport } from '@tabler/icons';
|
||||
import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||
import { uuid } from 'utils/common/index';
|
||||
|
||||
const ImportEnvironment = ({ onClose }) => {
|
||||
const ImportEnvironment = ({ onClose, onEnvironmentCreated }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleImportPostmanEnvironment = () => {
|
||||
@@ -37,17 +37,22 @@ const ImportEnvironment = ({ onClose }) => {
|
||||
})
|
||||
.then(() => {
|
||||
onClose();
|
||||
// Call the callback if provided
|
||||
if (onEnvironmentCreated) {
|
||||
onEnvironmentCreated();
|
||||
}
|
||||
})
|
||||
.catch((err) => toastError(err, 'Postman Import environment failed'));
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal size="sm" title="Import Global Environment" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
|
||||
<Modal size="sm" title="Import Global Environment" hideFooter={true} handleConfirm={onClose} handleCancel={onClose} dataTestId="import-global-environment-modal">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleImportPostmanEnvironment}
|
||||
className="flex justify-center flex-col items-center w-full dark:bg-zinc-700 rounded-lg border-2 border-dashed border-zinc-300 dark:border-zinc-400 p-12 text-center hover:border-zinc-400 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2"
|
||||
data-testid="import-postman-global-environment"
|
||||
>
|
||||
<IconDatabaseImport size={64} />
|
||||
<span className="mt-2 block text-sm font-semibold">Import your Postman environments</span>
|
||||
|
||||
@@ -39,7 +39,7 @@ const DefaultTab = ({ setTab }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const EnvironmentSettings = ({ globalEnvironments, activeGlobalEnvironmentUid, onClose }) => {
|
||||
const EnvironmentSettings = ({ globalEnvironments, onClose }) => {
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
const environments = globalEnvironments;
|
||||
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
||||
@@ -53,9 +53,8 @@ const EnvironmentSettings = ({ globalEnvironments, activeGlobalEnvironmentUid, o
|
||||
) : tab === 'import' ? (
|
||||
<ImportEnvironment onClose={() => setTab('default')} />
|
||||
) : (
|
||||
<></>
|
||||
<DefaultTab setTab={setTab} />
|
||||
)}
|
||||
<DefaultTab setTab={setTab} />
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
);
|
||||
@@ -65,7 +64,6 @@ const EnvironmentSettings = ({ globalEnvironments, activeGlobalEnvironmentUid, o
|
||||
<Modal size="lg" title="Global Environments" handleCancel={onClose} hideFooter={true}>
|
||||
<EnvironmentList
|
||||
environments={globalEnvironments}
|
||||
activeEnvironmentUid={activeGlobalEnvironmentUid}
|
||||
selectedEnvironment={selectedEnvironment}
|
||||
setSelectedEnvironment={setSelectedEnvironment}
|
||||
isModified={isModified}
|
||||
|
||||
@@ -71,7 +71,8 @@ const Modal = ({
|
||||
disableCloseOnOutsideClick,
|
||||
disableEscapeKey,
|
||||
onClick,
|
||||
closeModalFadeTimeout = 500
|
||||
closeModalFadeTimeout = 500,
|
||||
dataTestId
|
||||
}) => {
|
||||
const modalRef = useRef(null);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
@@ -120,6 +121,7 @@ const Modal = ({
|
||||
role="dialog"
|
||||
aria-labelledby="modal-title"
|
||||
aria-describedby="modal-description"
|
||||
data-testid={dataTestId}
|
||||
>
|
||||
<ModalHeader
|
||||
title={title}
|
||||
|
||||
@@ -147,7 +147,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
Save <span className="shortcut">({saveShortcut})</span>
|
||||
</span>
|
||||
</div>
|
||||
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} />
|
||||
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} data-testid="send-arrow-icon" />
|
||||
</div>
|
||||
</div>
|
||||
{generateCodeItemModalOpen && (
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import { uuid } from 'utils/common';
|
||||
import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
|
||||
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
||||
import GlobalEnvironmentSelector from 'components/GlobalEnvironments/EnvironmentSelector';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
@@ -69,9 +68,8 @@ const CollectionToolBar = ({ collection }) => {
|
||||
</ToolHint>
|
||||
</span>
|
||||
<span>
|
||||
<GlobalEnvironmentSelector />
|
||||
<EnvironmentSelector collection={collection} />
|
||||
</span>
|
||||
<EnvironmentSelector collection={collection} />
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -16,7 +16,7 @@ const StatusCode = ({ status }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className={`response-status-code ${getTabClassname(status)}`}>
|
||||
<StyledWrapper className={`response-status-code ${getTabClassname(status)}`} data-testid="response-status-code">
|
||||
{status} {statusCodePhraseMap[status]}
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -153,7 +153,7 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
|
||||
]
|
||||
|
||||
return (
|
||||
<Modal size="sm" title="Import Collection" hideFooter={true} handleCancel={onClose}>
|
||||
<Modal size="sm" title="Import Collection" hideFooter={true} handleCancel={onClose} dataTestId="import-collection-modal">
|
||||
<div className="flex flex-col">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">Import from file</h3>
|
||||
|
||||
@@ -48,7 +48,7 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) =>
|
||||
const onSubmit = () => formik.handleSubmit();
|
||||
|
||||
return (
|
||||
<Modal size="sm" title="Import Collection" confirmText="Import" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||
<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">
|
||||
|
||||
@@ -94,6 +94,7 @@ export const addGlobalEnvironment = ({ name, variables = [] }) => (dispatch, get
|
||||
ipcRenderer
|
||||
.invoke('renderer:create-global-environment', { name, uid, variables })
|
||||
.then(() => dispatch(_addGlobalEnvironment({ name, uid, variables })))
|
||||
.then(() => dispatch(_selectGlobalEnvironment({ environmentUid: uid })))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
|
||||
@@ -79,10 +79,16 @@ const darkTheme = {
|
||||
color: 'rgb(204, 204, 204)',
|
||||
iconColor: 'rgb(204, 204, 204)',
|
||||
bg: 'rgb(48, 48, 49)',
|
||||
hoverBg: '#185387',
|
||||
hoverBg: '#6A6A6A29',
|
||||
shadow: 'rgb(0 0 0 / 36%) 0px 2px 8px',
|
||||
separator: '#444',
|
||||
labelBg: '#4a4949'
|
||||
labelBg: '#4a4949',
|
||||
selectedBg: '#F59E0B14',
|
||||
selectedColor: '#F59E0B',
|
||||
mutedText: '#9B9B9B',
|
||||
primaryText: '#D4D4D4',
|
||||
secondaryText: '#9CA3AF',
|
||||
headingText: '#FFFFFF'
|
||||
},
|
||||
|
||||
request: {
|
||||
@@ -226,8 +232,8 @@ const darkTheme = {
|
||||
|
||||
tabs: {
|
||||
active: {
|
||||
color: '#ccc',
|
||||
border: '#569cd6'
|
||||
color: '#CCCCCC',
|
||||
border: '#F59E0B'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -79,10 +79,16 @@ const lightTheme = {
|
||||
color: 'rgb(48 48 48)',
|
||||
iconColor: 'rgb(75, 85, 99)',
|
||||
bg: '#fff',
|
||||
hoverBg: '#e9e9e9',
|
||||
hoverBg: '#e9ecef',
|
||||
shadow: 'rgb(50 50 93 / 25%) 0px 6px 12px -2px, rgb(0 0 0 / 30%) 0px 3px 7px -3px',
|
||||
separator: '#e7e7e7',
|
||||
labelBg: '#f3f3f3'
|
||||
labelBg: '#f3f3f3',
|
||||
selectedBg: '#D977060F',
|
||||
selectedColor: '#D97706',
|
||||
mutedText: '#9B9B9B',
|
||||
primaryText: '#343434',
|
||||
secondaryText: '#6B7280',
|
||||
headingText: '#343434'
|
||||
},
|
||||
|
||||
request: {
|
||||
@@ -227,8 +233,8 @@ const lightTheme = {
|
||||
|
||||
tabs: {
|
||||
active: {
|
||||
color: 'rgb(50, 46, 44)',
|
||||
border: '#546de5'
|
||||
color: '#343434',
|
||||
border: '#D97706'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ test.describe.serial('bru.setEnvVar(name, value, { persist: true })', () => {
|
||||
await page.getByText('api-setEnvVar-with-persist', { exact: true }).click();
|
||||
|
||||
// open environment dropdown
|
||||
await page.locator('div.current-environment.collection-environment').click();
|
||||
await page.locator('div.current-environment').click();
|
||||
|
||||
// select stage environment
|
||||
await expect(page.locator('.dropdown-item').filter({ hasText: 'Stage' })).toBeVisible();
|
||||
@@ -27,7 +27,8 @@ test.describe.serial('bru.setEnvVar(name, value, { persist: true })', () => {
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// confirm that the environment variable is set
|
||||
await page.getByTitle('Stage', { exact: true }).click();
|
||||
await page.locator('div.current-environment').click();
|
||||
|
||||
await page.getByText('Configure', { exact: true }).click();
|
||||
await expect(page.getByRole('row', { name: 'token' }).getByRole('cell').nth(1)).toBeVisible();
|
||||
await expect(page.getByRole('row', { name: 'secret' }).getByRole('cell').nth(2)).toBeVisible();
|
||||
@@ -42,7 +43,7 @@ test.describe.serial('bru.setEnvVar(name, value, { persist: true })', () => {
|
||||
await newPage.getByText('api-setEnvVar-with-persist', { exact: true }).click();
|
||||
|
||||
// open environment dropdown
|
||||
await newPage.locator('div.current-environment.collection-environment').click();
|
||||
await newPage.locator('div.current-environment').click();
|
||||
await newPage.getByText('Configure', { exact: true }).click();
|
||||
await expect(newPage.getByRole('row', { name: 'token' }).getByRole('cell').nth(1)).toBeVisible();
|
||||
await expect(newPage.getByRole('row', { name: 'secret' }).getByRole('cell').nth(2)).toBeVisible();
|
||||
|
||||
@@ -9,7 +9,7 @@ test.describe.serial('bru.setEnvVar(name, value)', () => {
|
||||
await page.getByText('api-setEnvVar-without-persist', { exact: true }).click();
|
||||
|
||||
// open environment dropdown
|
||||
await page.locator('div.current-environment.collection-environment').click();
|
||||
await page.locator('div.current-environment').click();
|
||||
|
||||
// select stage environment
|
||||
await expect(page.locator('.dropdown-item').filter({ hasText: 'Stage' })).toBeVisible();
|
||||
@@ -21,7 +21,7 @@ test.describe.serial('bru.setEnvVar(name, value)', () => {
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// confirm that the environment variable is set
|
||||
await page.getByTitle('Stage', { exact: true }).click();
|
||||
await page.locator('div.current-environment').click();
|
||||
await page.getByText('Configure', { exact: true }).click();
|
||||
await expect(page.getByRole('row', { name: 'token' }).getByRole('cell').nth(1)).toBeVisible();
|
||||
await expect(page.getByRole('row', { name: 'secret' }).getByRole('cell').nth(2)).toBeVisible();
|
||||
@@ -36,7 +36,7 @@ test.describe.serial('bru.setEnvVar(name, value)', () => {
|
||||
await newPage.getByText('api-setEnvVar-without-persist', { exact: true }).click();
|
||||
|
||||
// open environment dropdown
|
||||
await newPage.locator('div.current-environment.collection-environment').click();
|
||||
await newPage.locator('div.current-environment').click();
|
||||
await newPage.getByText('Configure', { exact: true }).click();
|
||||
|
||||
// ensure that the environment variable is not persisted
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import path from 'path';
|
||||
|
||||
test.describe('Collection Environment Create Tests', () => {
|
||||
test('should import collection and create environment for request usage', async ({
|
||||
pageWithUserData: page,
|
||||
createTmpDir
|
||||
}) => {
|
||||
const openApiFile = path.join(__dirname, 'fixtures', 'bruno-collection.json');
|
||||
|
||||
// Import test collection
|
||||
await page.getByRole('button', { name: 'Import Collection' }).click();
|
||||
|
||||
const importModal = page.locator('[data-testid="import-collection-modal"]');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
|
||||
await page.setInputFiles('input[type="file"]', openApiFile);
|
||||
|
||||
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
|
||||
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
|
||||
await expect(locationModal.getByText('test_collection')).toBeVisible();
|
||||
|
||||
await page.locator('#collection-location').fill(await createTmpDir('env-test'));
|
||||
await page.getByRole('button', { name: 'Import', exact: true }).click();
|
||||
|
||||
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'test_collection' })).toBeVisible();
|
||||
|
||||
// Configure collection
|
||||
await page.locator('#sidebar-collection-name').filter({ hasText: 'test_collection' }).click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
// Create environment
|
||||
await page.locator('[data-testid="environment-selector-trigger"]').click();
|
||||
await expect(page.locator('[data-testid="env-tab-collection"]')).toHaveClass(/active/);
|
||||
|
||||
// Create new environment
|
||||
await page.locator('button[id="create-env"]').click();
|
||||
|
||||
// Fill environment name
|
||||
const environmentNameInput = page.locator('input[name="name"]');
|
||||
await expect(environmentNameInput).toBeVisible();
|
||||
await environmentNameInput.fill('Test Environment');
|
||||
await page.getByRole('button', { name: 'Create' }).click();
|
||||
|
||||
// Add environment variables
|
||||
await page.locator('button[data-testid="add-variable"]').click();
|
||||
await page.locator('input[name="0.name"]').fill('host');
|
||||
await page
|
||||
.locator('tr')
|
||||
.filter({ has: page.locator('input[name="0.name"]') })
|
||||
.locator('.CodeMirror')
|
||||
.click();
|
||||
await page.keyboard.type('https://echo.usebruno.com');
|
||||
|
||||
// Add userId
|
||||
await page.locator('button[data-testid="add-variable"]').click();
|
||||
await page.locator('input[name="1.name"]').fill('userId');
|
||||
await page
|
||||
.locator('tr')
|
||||
.filter({ has: page.locator('input[name="1.name"]') })
|
||||
.locator('.CodeMirror')
|
||||
.click();
|
||||
await page.keyboard.type('1');
|
||||
|
||||
// Add postTitle
|
||||
await page.locator('button[data-testid="add-variable"]').click();
|
||||
await page.locator('input[name="2.name"]').fill('postTitle');
|
||||
await page
|
||||
.locator('tr')
|
||||
.filter({ has: page.locator('input[name="2.name"]') })
|
||||
.locator('.CodeMirror')
|
||||
.click();
|
||||
await page.keyboard.type('Test Post from Environment');
|
||||
|
||||
// Add postBody
|
||||
await page.locator('button[data-testid="add-variable"]').click();
|
||||
await page.locator('input[name="3.name"]').fill('postBody');
|
||||
await page
|
||||
.locator('tr')
|
||||
.filter({ has: page.locator('input[name="3.name"]') })
|
||||
.locator('.CodeMirror')
|
||||
.click();
|
||||
await page.keyboard.type('This is a test post body with environment variables');
|
||||
|
||||
// Add secret token
|
||||
await page.locator('button[data-testid="add-variable"]').click();
|
||||
await page.locator('input[name="4.name"]').fill('secretApiToken');
|
||||
await page
|
||||
.locator('tr')
|
||||
.filter({ has: page.locator('input[name="4.name"]') })
|
||||
.locator('.CodeMirror')
|
||||
.click();
|
||||
await page.keyboard.type('super-secret-token-12345');
|
||||
await page.locator('input[name="4.secret"]').check();
|
||||
|
||||
// Save environment
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await page.getByText('×').click();
|
||||
await expect(page.locator('.current-environment')).toContainText('Test Environment');
|
||||
|
||||
// Test GET request with environment variables
|
||||
await page.locator('.collection-item-name').first().click();
|
||||
await expect(page.locator('#request-url .CodeMirror-line')).toContainText('{{host}}');
|
||||
await page.locator('[data-testid="send-arrow-icon"]').click();
|
||||
await page.locator('[data-testid="response-status-code"]').waitFor({ state: 'visible' });
|
||||
await expect(page.locator('[data-testid="response-status-code"]')).toContainText('200');
|
||||
|
||||
// Verify the JSON response contains the environment variables
|
||||
const responsePane = page.locator('.response-pane');
|
||||
await expect(responsePane).toContainText('"userId": 1');
|
||||
await expect(responsePane).toContainText('"title": "Test Post from Environment"');
|
||||
await expect(responsePane).toContainText('"body": "This is a test post body with environment variables"');
|
||||
await expect(responsePane).toContainText('"apiToken": "super-secret-token-12345"');
|
||||
|
||||
// Cleanup
|
||||
await page
|
||||
.locator('.collection-name')
|
||||
.filter({ has: page.locator('#sidebar-collection-name:has-text("test_collection")') })
|
||||
.locator('.collection-actions')
|
||||
.click();
|
||||
await page.locator('.dropdown-item').filter({ hasText: 'Close' }).click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
await page.locator('.bruno-logo').click();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "test_collection",
|
||||
"version": "1",
|
||||
"items": [
|
||||
{
|
||||
"type": "http",
|
||||
"name": "test",
|
||||
"filename": "test.bru",
|
||||
"seq": 1,
|
||||
"settings": {
|
||||
"encodeUrl": true
|
||||
},
|
||||
"tags": [],
|
||||
"request": {
|
||||
"url": "{{host}}",
|
||||
"method": "POST",
|
||||
"headers": [],
|
||||
"params": [],
|
||||
"body": {
|
||||
"mode": "json",
|
||||
"json": "{\n \"userId\": {{userId}},\n \"title\": \"{{postTitle}}\",\n \"body\": \"{{postBody}}\",\n \"apiToken\": \"{{secretApiToken}}\"\n}",
|
||||
"formUrlEncoded": [],
|
||||
"multipartForm": [],
|
||||
"file": []
|
||||
},
|
||||
"script": {},
|
||||
"vars": {},
|
||||
"assertions": [],
|
||||
"tests": "",
|
||||
"docs": "",
|
||||
"auth": {
|
||||
"mode": "inherit"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"environments": [],
|
||||
"brunoConfig": {
|
||||
"version": "1",
|
||||
"name": "test_collection",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
],
|
||||
"size": 0.000133514404296875,
|
||||
"filesCount": 1,
|
||||
"proxy": {
|
||||
"bypassProxy": "",
|
||||
"enabled": false,
|
||||
"auth": {
|
||||
"enabled": false,
|
||||
"username": "",
|
||||
"password": ""
|
||||
},
|
||||
"port": null,
|
||||
"hostname": "",
|
||||
"protocol": "http"
|
||||
}
|
||||
}
|
||||
}
|
||||
130
tests/environments/create-environment/global-env-create.spec.ts
Normal file
130
tests/environments/create-environment/global-env-create.spec.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import path from 'path';
|
||||
|
||||
test.describe('Global Environment Create Tests', () => {
|
||||
test('should import collection and create global environment for request usage', async ({
|
||||
pageWithUserData: page,
|
||||
createTmpDir
|
||||
}) => {
|
||||
const openApiFile = path.join(__dirname, 'fixtures', 'bruno-collection.json');
|
||||
|
||||
// Import test collection
|
||||
await page.getByRole('button', { name: 'Import Collection' }).click();
|
||||
|
||||
const importModal = page.locator('[data-testid="import-collection-modal"]');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
|
||||
await page.setInputFiles('input[type="file"]', openApiFile);
|
||||
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
|
||||
|
||||
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
|
||||
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
|
||||
await expect(locationModal.getByText('test_collection')).toBeVisible();
|
||||
|
||||
await page.locator('#collection-location').fill(await createTmpDir('global-env-test'));
|
||||
await page.getByRole('button', { name: 'Import', exact: true }).click();
|
||||
|
||||
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'test_collection' })).toBeVisible();
|
||||
|
||||
// Configure collection
|
||||
await page.locator('#sidebar-collection-name').filter({ hasText: 'test_collection' }).click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
// Create global environment
|
||||
await page.locator('[data-testid="environment-selector-trigger"]').click();
|
||||
await page.locator('[data-testid="env-tab-global"]').click();
|
||||
await expect(page.locator('[data-testid="env-tab-global"]')).toHaveClass(/active/);
|
||||
|
||||
// Create new global environment
|
||||
await page.locator('button[id="create-env"]').click();
|
||||
|
||||
// Fill environment name
|
||||
const environmentNameInput = page.locator('input[name="name"]');
|
||||
await expect(environmentNameInput).toBeVisible();
|
||||
await environmentNameInput.fill('Test Global Environment');
|
||||
await page.getByRole('button', { name: 'Create' }).click();
|
||||
|
||||
// Add environment variables
|
||||
await page.locator('button[data-testid="add-variable"]').click();
|
||||
await page.locator('input[name="0.name"]').fill('host');
|
||||
await page
|
||||
.locator('tr')
|
||||
.filter({ has: page.locator('input[name="0.name"]') })
|
||||
.locator('.CodeMirror')
|
||||
.click();
|
||||
await page.keyboard.type('https://echo.usebruno.com');
|
||||
|
||||
// Add userId
|
||||
await page.locator('button[data-testid="add-variable"]').click();
|
||||
await page.locator('input[name="1.name"]').fill('userId');
|
||||
await page
|
||||
.locator('tr')
|
||||
.filter({ has: page.locator('input[name="1.name"]') })
|
||||
.locator('.CodeMirror')
|
||||
.click();
|
||||
await page.keyboard.type('1');
|
||||
|
||||
// Add postTitle
|
||||
await page.locator('button[data-testid="add-variable"]').click();
|
||||
await page.locator('input[name="2.name"]').fill('postTitle');
|
||||
await page
|
||||
.locator('tr')
|
||||
.filter({ has: page.locator('input[name="2.name"]') })
|
||||
.locator('.CodeMirror')
|
||||
.click();
|
||||
await page.keyboard.type('Global Test Post from Environment');
|
||||
|
||||
// Add postBody
|
||||
await page.locator('button[data-testid="add-variable"]').click();
|
||||
await page.locator('input[name="3.name"]').fill('postBody');
|
||||
await page
|
||||
.locator('tr')
|
||||
.filter({ has: page.locator('input[name="3.name"]') })
|
||||
.locator('.CodeMirror')
|
||||
.click();
|
||||
await page.keyboard.type('This is a global test post body with environment variables');
|
||||
|
||||
// Add secret token
|
||||
await page.locator('button[data-testid="add-variable"]').click();
|
||||
await page.locator('input[name="4.name"]').fill('secretApiToken');
|
||||
await page
|
||||
.locator('tr')
|
||||
.filter({ has: page.locator('input[name="4.name"]') })
|
||||
.locator('.CodeMirror')
|
||||
.click();
|
||||
await page.keyboard.type('global-secret-token-12345');
|
||||
await page.locator('input[name="4.secret"]').check();
|
||||
await expect(page.locator('input[name="4.secret"]')).toBeChecked();
|
||||
|
||||
// Save environment
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await page.getByText('×').click();
|
||||
await expect(page.locator('.current-environment')).toContainText('Test Global Environment');
|
||||
|
||||
// Test GET request with environment variables
|
||||
await page.locator('.collection-item-name').first().click();
|
||||
await expect(page.locator('#request-url .CodeMirror-line')).toContainText('{{host}}');
|
||||
await page.locator('[data-testid="send-arrow-icon"]').click();
|
||||
await page.locator('[data-testid="response-status-code"]').waitFor({ state: 'visible' });
|
||||
await expect(page.locator('[data-testid="response-status-code"]')).toContainText('200');
|
||||
|
||||
// Verify the JSON response contains the environment variables
|
||||
const responsePane = page.locator('.response-pane');
|
||||
await expect(responsePane).toContainText('"userId": 1');
|
||||
await expect(responsePane).toContainText('"title": "Global Test Post from Environment"');
|
||||
await expect(responsePane).toContainText('"body": "This is a global test post body with environment variables"');
|
||||
await expect(responsePane).toContainText('"apiToken": "global-secret-token-12345"');
|
||||
|
||||
// Cleanup
|
||||
await page
|
||||
.locator('.collection-name')
|
||||
.filter({ has: page.locator('#sidebar-collection-name:has-text("test_collection")') })
|
||||
.locator('.collection-actions')
|
||||
.click();
|
||||
await page.locator('.dropdown-item').filter({ hasText: 'Close' }).click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
await page.locator('.bruno-logo').click();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import path from 'path';
|
||||
|
||||
test.describe('Collection Environment Import Tests', () => {
|
||||
test('should import collection environment from file', async ({ pageWithUserData: page, createTmpDir }) => {
|
||||
const openApiFile = path.join(__dirname, 'fixtures', 'collection.json');
|
||||
const envFile = path.join(__dirname, 'fixtures', 'collection-env.json');
|
||||
|
||||
// Import test collection
|
||||
await page.getByRole('button', { name: 'Import Collection' }).click();
|
||||
|
||||
const importModal = page.locator('[data-testid="import-collection-modal"]');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
|
||||
await page.setInputFiles('input[type="file"]', openApiFile);
|
||||
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
|
||||
|
||||
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
|
||||
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
|
||||
await expect(locationModal.getByText('Environment Test Collection')).toBeVisible();
|
||||
|
||||
await page.locator('#collection-location').fill(await createTmpDir('collection-env-import-test'));
|
||||
await page.getByRole('button', { name: 'Import', exact: true }).click();
|
||||
|
||||
await expect(
|
||||
page.locator('#sidebar-collection-name').filter({ hasText: 'Environment Test Collection' })
|
||||
).toBeVisible();
|
||||
|
||||
// Configure collection
|
||||
await page.locator('#sidebar-collection-name').filter({ hasText: 'Environment Test Collection' }).click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
// Import collection environment
|
||||
await page.locator('[data-testid="environment-selector-trigger"]').click();
|
||||
await expect(page.locator('[data-testid="env-tab-collection"]')).toHaveClass(/active/);
|
||||
await page.locator('button[id="import-env"]').click();
|
||||
const importEnvModal = page.locator('[data-testid="import-environment-modal"]');
|
||||
await expect(importEnvModal).toBeVisible();
|
||||
|
||||
// Import environment file
|
||||
const fileChooserPromise = page.waitForEvent('filechooser');
|
||||
await page.locator('button[data-testid="import-postman-environment"]').click();
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(envFile);
|
||||
|
||||
// Wait for import to complete and environment settings modal to open
|
||||
await expect(page.locator('.current-environment')).toContainText('Test Collection Environment');
|
||||
|
||||
// The environment settings modal should now be visible with the imported environment
|
||||
const envSettingsModal = page.locator('.bruno-modal').filter({ hasText: 'Environments' });
|
||||
await expect(envSettingsModal).toBeVisible();
|
||||
|
||||
// Verify imported variables in Test Collection Environment settings
|
||||
await expect(envSettingsModal.locator('input[name="0.name"]')).toHaveValue('host');
|
||||
await expect(envSettingsModal.locator('input[name="1.name"]')).toHaveValue('userId');
|
||||
await expect(envSettingsModal.locator('input[name="2.name"]')).toHaveValue('apiKey');
|
||||
await expect(envSettingsModal.locator('input[name="3.name"]')).toHaveValue('postTitle');
|
||||
await expect(envSettingsModal.locator('input[name="4.name"]')).toHaveValue('postBody');
|
||||
await expect(envSettingsModal.locator('input[name="5.name"]')).toHaveValue('secretApiToken');
|
||||
await expect(envSettingsModal.locator('input[name="5.secret"]')).toBeChecked();
|
||||
await page.getByText('×').click();
|
||||
|
||||
// Test GET request with imported environment
|
||||
await page.locator('.collection-item-name').first().click();
|
||||
await expect(page.locator('#request-url .CodeMirror-line')).toContainText('{{host}}/posts/{{userId}}');
|
||||
await page.locator('[data-testid="send-arrow-icon"]').click();
|
||||
await page.locator('[data-testid="response-status-code"]').waitFor({ state: 'visible' });
|
||||
await expect(page.locator('[data-testid="response-status-code"]')).toContainText('200');
|
||||
|
||||
// Verify the JSON response contains the interpolated userId
|
||||
const responsePane = page.locator('.response-pane');
|
||||
await expect(responsePane).toContainText('"userId": 1');
|
||||
|
||||
// Test POST request
|
||||
await page.locator('.collection-item-name').nth(1).click();
|
||||
await expect(page.locator('#request-url .CodeMirror-line')).toContainText('{{host}}/posts');
|
||||
await page.locator('[data-testid="send-arrow-icon"]').click();
|
||||
await page.locator('[data-testid="response-status-code"]').waitFor({ state: 'visible' });
|
||||
await expect(page.locator('[data-testid="response-status-code"]')).toContainText('201');
|
||||
|
||||
// Cleanup
|
||||
await page.locator('#sidebar-collection-name').filter({ hasText: 'Environment Test Collection' }).click();
|
||||
await page
|
||||
.locator('.collection-name')
|
||||
.filter({ has: page.locator('#sidebar-collection-name:has-text("Environment Test Collection")') })
|
||||
.locator('.collection-actions')
|
||||
.click();
|
||||
await page.locator('.dropdown-item').filter({ hasText: 'Close' }).click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
await page.locator('.bruno-logo').click();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"id": "test-collection-env-id",
|
||||
"name": "Test Collection Environment",
|
||||
"values": [
|
||||
{
|
||||
"key": "host",
|
||||
"value": "https://jsonplaceholder.typicode.com",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "userId",
|
||||
"value": "1",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "apiKey",
|
||||
"value": "collection-api-key-12345",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "postTitle",
|
||||
"value": "Collection Environment Test Post",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "postBody",
|
||||
"value": "This is a test post created using collection environment variables",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "secretApiToken",
|
||||
"value": "collection-secret-token-67890",
|
||||
"enabled": true,
|
||||
"type": "secret"
|
||||
}
|
||||
],
|
||||
"_postman_variable_scope": "environment",
|
||||
"_postman_exported_at": "2024-01-01T00:00:00.000Z",
|
||||
"_postman_exported_using": "Postman/10.0.0"
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"info": {
|
||||
"name": "Environment Test Collection",
|
||||
"description": "Test collection for environment import and usage tests",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||
"_postman_id": "env-test-collection-id"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Get Posts with Environment Variables",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{apiKey}}",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "X-Secret-Token",
|
||||
"value": "{{secretApiToken}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{host}}/posts/{{userId}}",
|
||||
"host": ["{{host}}"],
|
||||
"path": ["posts", "{{userId}}"]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create Post with Body Variables",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{apiKey}}",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "X-Secret-Token",
|
||||
"value": "{{secretApiToken}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"title\": \"{{postTitle}}\",\n \"body\": \"{{postBody}}\",\n \"userId\": {{userId}}\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{host}}/posts",
|
||||
"host": ["{{host}}"],
|
||||
"path": ["posts"]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"id": "test-global-env-id",
|
||||
"name": "Test Global Environment",
|
||||
"values": [
|
||||
{
|
||||
"key": "host",
|
||||
"value": "https://jsonplaceholder.typicode.com",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "userId",
|
||||
"value": "1",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "apiKey",
|
||||
"value": "global-api-key-12345",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "postTitle",
|
||||
"value": "Global Test Post from Environment",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "postBody",
|
||||
"value": "This is a global test post body with environment variables",
|
||||
"enabled": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "secretApiToken",
|
||||
"value": "global-secret-token-67890",
|
||||
"enabled": true,
|
||||
"type": "secret"
|
||||
}
|
||||
],
|
||||
"_postman_variable_scope": "globals",
|
||||
"_postman_exported_at": "2024-01-01T00:00:00.000Z",
|
||||
"_postman_exported_using": "Postman/10.0.0"
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import path from 'path';
|
||||
|
||||
test.describe('Global Environment Import Tests', () => {
|
||||
test('should import global environment from file', async ({ pageWithUserData: page, createTmpDir }) => {
|
||||
const openApiFile = path.join(__dirname, 'fixtures', 'collection.json');
|
||||
const globalEnvFile = path.join(__dirname, 'fixtures', 'global-env.json');
|
||||
|
||||
// Import test collection
|
||||
await page.getByRole('button', { name: 'Import Collection' }).click();
|
||||
|
||||
const importModal = page.locator('[data-testid="import-collection-modal"]');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
|
||||
await page.setInputFiles('input[type="file"]', openApiFile);
|
||||
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
|
||||
|
||||
const locationModal = page.locator('[data-testid="import-collection-location-modal"]');
|
||||
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
|
||||
await expect(locationModal.getByText('Environment Test Collection')).toBeVisible();
|
||||
|
||||
await page.locator('#collection-location').fill(await createTmpDir('global-env-import-test'));
|
||||
await page.getByRole('button', { name: 'Import', exact: true }).click();
|
||||
|
||||
await expect(
|
||||
page.locator('#sidebar-collection-name').filter({ hasText: 'Environment Test Collection' })
|
||||
).toBeVisible();
|
||||
|
||||
// Configure collection
|
||||
await page.locator('#sidebar-collection-name').filter({ hasText: 'Environment Test Collection' }).click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
// Import global environment
|
||||
await page.locator('[data-testid="environment-selector-trigger"]').click();
|
||||
await page.locator('[data-testid="env-tab-global"]').click();
|
||||
await expect(page.locator('[data-testid="env-tab-global"]')).toHaveClass(/active/);
|
||||
await page.locator('button[id="import-env"]').click();
|
||||
const importGlobalEnvModal = page.locator('[data-testid="import-global-environment-modal"]');
|
||||
await expect(importGlobalEnvModal).toBeVisible();
|
||||
|
||||
// Import environment file
|
||||
const fileChooserPromise = page.waitForEvent('filechooser');
|
||||
await page.locator('button[data-testid="import-postman-global-environment"]').click();
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(globalEnvFile);
|
||||
|
||||
// Wait for import to complete and global environment settings modal to open
|
||||
await expect(page.locator('.current-environment')).toContainText('Test Global Environment');
|
||||
|
||||
// The global environment settings modal should now be visible with the imported environment
|
||||
const globalEnvSettingsModal = page.locator('.bruno-modal').filter({ hasText: 'Global Environments' });
|
||||
await expect(globalEnvSettingsModal).toBeVisible();
|
||||
|
||||
// Verify imported variables in Test Global Environment settings
|
||||
await expect(globalEnvSettingsModal.locator('input[name="0.name"]')).toHaveValue('host');
|
||||
await expect(globalEnvSettingsModal.locator('input[name="1.name"]')).toHaveValue('userId');
|
||||
await expect(globalEnvSettingsModal.locator('input[name="2.name"]')).toHaveValue('apiKey');
|
||||
await expect(globalEnvSettingsModal.locator('input[name="3.name"]')).toHaveValue('postTitle');
|
||||
await expect(globalEnvSettingsModal.locator('input[name="4.name"]')).toHaveValue('postBody');
|
||||
await expect(globalEnvSettingsModal.locator('input[name="5.name"]')).toHaveValue('secretApiToken');
|
||||
await expect(globalEnvSettingsModal.locator('input[name="5.secret"]')).toBeChecked();
|
||||
await page.getByText('×').click();
|
||||
|
||||
// Test GET request with global environment
|
||||
await page.locator('.collection-item-name').first().click();
|
||||
await expect(page.locator('#request-url .CodeMirror-line')).toContainText('{{host}}/posts/{{userId}}');
|
||||
await page.locator('[data-testid="send-arrow-icon"]').click();
|
||||
await page.locator('[data-testid="response-status-code"]').waitFor({ state: 'visible' });
|
||||
await expect(page.locator('[data-testid="response-status-code"]')).toContainText('200');
|
||||
|
||||
// Verify the JSON response contains the interpolated userId
|
||||
const responsePane = page.locator('.response-pane');
|
||||
await expect(responsePane).toContainText('"userId": 1');
|
||||
|
||||
// Test POST request
|
||||
await page.locator('.collection-item-name').nth(1).click();
|
||||
await expect(page.locator('#request-url .CodeMirror-line')).toContainText('{{host}}/posts');
|
||||
await page.locator('[data-testid="send-arrow-icon"]').click();
|
||||
await page.locator('[data-testid="response-status-code"]').waitFor({ state: 'visible' });
|
||||
await expect(page.locator('[data-testid="response-status-code"]')).toContainText('201');
|
||||
|
||||
// Cleanup
|
||||
await page.locator('#sidebar-collection-name').filter({ hasText: 'Environment Test Collection' }).click();
|
||||
await page
|
||||
.locator('.collection-name')
|
||||
.filter({ has: page.locator('#sidebar-collection-name:has-text("Environment Test Collection")') })
|
||||
.locator('.collection-actions')
|
||||
.click();
|
||||
await page.locator('.dropdown-item').filter({ hasText: 'Close' }).click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
await page.locator('.bruno-logo').click();
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,7 @@ test.describe('Multiline Variables - Read Environment Test', () => {
|
||||
await page.getByTitle('request', { exact: true }).click();
|
||||
|
||||
// open environment dropdown
|
||||
await page.locator('div.current-environment.collection-environment').click();
|
||||
await page.locator('div.current-environment').click();
|
||||
|
||||
// select test environment
|
||||
await expect(page.locator('.dropdown-item').filter({ hasText: 'Test' })).toBeVisible();
|
||||
|
||||
@@ -13,7 +13,7 @@ test.describe('Multiline Variables - Write Test', () => {
|
||||
await page.getByTitle('multiline-test', { exact: true }).click();
|
||||
|
||||
// open environment dropdown
|
||||
await page.locator('div.current-environment.collection-environment').click();
|
||||
await page.locator('div.current-environment').click();
|
||||
|
||||
// select test environment
|
||||
await expect(page.locator('.dropdown-item').filter({ hasText: 'Test' })).toBeVisible();
|
||||
@@ -21,12 +21,11 @@ test.describe('Multiline Variables - Write Test', () => {
|
||||
await expect(page.locator('.current-environment').filter({ hasText: /Test/ })).toBeVisible();
|
||||
|
||||
// select configure button from environment dropdown
|
||||
await expect(page.getByTitle('Test', { exact: true })).toBeVisible();
|
||||
await page.getByTitle('Test', { exact: true }).click();
|
||||
await page.locator('div.current-environment').click();
|
||||
|
||||
// open environment configuration
|
||||
await expect(page.locator('#Configure')).toBeVisible();
|
||||
await page.locator('#Configure').click();
|
||||
await expect(page.getByText('Configure', { exact: true })).toBeVisible();
|
||||
await page.getByText('Configure', { exact: true }).click();
|
||||
|
||||
// add variable
|
||||
await page.getByRole('button', { name: /Add.*Variable/i }).click();
|
||||
|
||||
@@ -6,22 +6,22 @@ test.describe('Invalid File Handling', () => {
|
||||
|
||||
test('Handle invalid file without crashing', async ({ page }) => {
|
||||
const invalidFile = path.join(testDataDir, 'invalid.txt');
|
||||
|
||||
|
||||
await page.getByRole('button', { name: 'Import Collection' }).click();
|
||||
|
||||
|
||||
// Wait for import collection modal to be ready
|
||||
const importModal = page.getByRole('dialog');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
|
||||
|
||||
|
||||
await page.setInputFiles('input[type="file"]', invalidFile);
|
||||
|
||||
// Wait for the loader to disappear
|
||||
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
|
||||
|
||||
const hasError = await page.getByText("Failed to parse the file – ensure it is valid JSON or YAML").isVisible();
|
||||
|
||||
const hasError = await page.getByText("Failed to parse the file – ensure it is valid JSON or YAML").first().isVisible();
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
|
||||
// Cleanup: close any open modals
|
||||
await page.locator('[data-test-id="modal-close-button"]').click();
|
||||
});
|
||||
|
||||
@@ -8,19 +8,19 @@ test.describe('Invalid Postman Collection - Missing Info', () => {
|
||||
const postmanFile = path.join(testDataDir, 'postman-invalid-missing-info.json');
|
||||
|
||||
await page.getByRole('button', { name: 'Import Collection' }).click();
|
||||
|
||||
|
||||
// Wait for import collection modal to be ready
|
||||
const importModal = page.getByRole('dialog');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
await expect(importModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
|
||||
|
||||
|
||||
await page.setInputFiles('input[type="file"]', postmanFile);
|
||||
|
||||
// Wait for the loader to disappear
|
||||
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
|
||||
|
||||
|
||||
// Check for error message
|
||||
const hasError = await page.getByText('Import collection failed').isVisible();
|
||||
const hasError = await page.getByText('Import collection failed').first().isVisible();
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
// Cleanup: close any open modals
|
||||
|
||||
Reference in New Issue
Block a user