- {environments
- && environments.length
- && environments.map((env) => (
-
+ {switchEnvConfirmClose && (
+
+ handleConfirmSwitch(false)} />
+
+ )}
-
-
handleImportClick()}>
-
- Import
-
-
setShowExportModal(true)}>
-
- Export
-
-
handleSecretsClick()}>
-
- Managing Secrets
-
+
+
+
Environments
+
+
+
+
+
+
+
+ setSearchText(e.target.value)}
+ className="search-input"
+ />
+
+
+
+ {filteredEnvironments.map((env) => (
+
renamingEnvUid !== env.uid && handleEnvironmentClick(env)}
+ onDoubleClick={() => handleEnvironmentDoubleClick(env)}
+ >
+ {renamingEnvUid === env.uid ? (
+
+
+
+
+
+
+
+ ) : (
+ <>
+
{env.name}
+
+ {activeEnvironmentUid === env.uid ? (
+
+
+
+ ) : (
+
+ )}
+
+ >
+ )}
+
+ ))}
+
+ {isCreatingInline && (
+
+
+
+
+
+
+
+ )}
+
+ {envNameError && (isCreatingInline || renamingEnvUid) &&
{envNameError}
}
+
+
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/ManageSecrets/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/ManageSecrets/index.js
deleted file mode 100644
index de50ad92b..000000000
--- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/ManageSecrets/index.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import Portal from 'components/Portal';
-import Modal from 'components/Modal';
-
-const ManageSecrets = ({ onClose }) => {
- return (
-
-
-
-
In any collection, there are secrets that need to be managed.
-
These secrets can be anything such as API keys, passwords, or tokens.
-
Bruno offers three approaches to manage secrets in collections.
-
- Read more about it in our{' '}
-
- docs
-
- .
-
-
-
-
- );
-};
-
-export default ManageSecrets;
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js
deleted file mode 100644
index dc10c8293..000000000
--- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/RenameEnvironment/index.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import React, { useEffect, useRef } from 'react';
-import Portal from 'components/Portal/index';
-import Modal from 'components/Modal/index';
-import toast from 'react-hot-toast';
-import { useFormik } from 'formik';
-import { renameEnvironment } from 'providers/ReduxStore/slices/collections/actions';
-import * as Yup from 'yup';
-import { useDispatch } from 'react-redux';
-import { validateName, validateNameError } from 'utils/common/regex';
-
-const RenameEnvironment = ({ onClose, environment, collection }) => {
- const dispatch = useDispatch();
- const inputRef = useRef();
- const formik = useFormik({
- enableReinitialize: true,
- initialValues: {
- name: environment.name
- },
- validationSchema: Yup.object({
- name: Yup.string()
- .min(1, 'must be at least 1 character')
- .max(255, 'Must be 255 characters or less')
- .test('is-valid-filename', function (value) {
- const isValid = validateName(value);
- return isValid ? true : this.createError({ message: validateNameError(value) });
- })
- .required('name is required')
- }),
- onSubmit: (values) => {
- if (values.name === environment.name) {
- return;
- }
- dispatch(renameEnvironment(values.name, environment.uid, collection.uid))
- .then(() => {
- toast.success('Environment renamed successfully');
- onClose();
- })
- .catch(() => toast.error('An error occurred while renaming the environment'));
- }
- });
-
- useEffect(() => {
- if (inputRef && inputRef.current) {
- inputRef.current.focus();
- }
- }, [inputRef]);
-
- const onSubmit = () => {
- formik.handleSubmit();
- };
-
- return (
-
-
-
-
-
- );
-};
-
-export default RenameEnvironment;
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js
index 2dfad0cfe..4ddcc5586 100644
--- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/StyledWrapper.js
@@ -1,11 +1,51 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
- button.btn-create-environment {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ background-color: ${(props) => props.theme.bg};
+
+ .empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ flex: 1;
+ color: ${(props) => props.theme.colors.text.muted};
+
+ svg {
+ opacity: 0.3;
+ margin-bottom: 8px;
+ }
+
+ .title {
+ font-size: 13px;
+ font-weight: 500;
+ margin-bottom: 12px;
+ color: ${(props) => props.theme.colors.text.muted};
+ }
+
+ .actions {
+ display: flex;
+ gap: 8px;
+ }
+ }
+
+ .shared-button {
+ padding: 5px 10px;
+ font-size: 12px;
+ border-radius: 5px;
+ border: 1px solid ${(props) => props.theme.sidebar.collection.item.indentBorder};
+ background: ${(props) => props.theme.sidebar.bg};
+ color: ${(props) => props.theme.text};
+ cursor: pointer;
+ transition: all 0.1s ease;
+
&:hover {
- span {
- text-decoration: underline;
- }
+ background: ${(props) => props.theme.listItem.hoverBg};
+ border-color: ${(props) => props.theme.textLink};
}
}
`;
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js
index 8690f7053..2d3492087 100644
--- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js
@@ -1,87 +1,64 @@
-import Modal from 'components/Modal/index';
import React, { useState } from 'react';
-import CreateEnvironment from './CreateEnvironment';
+import CreateEnvironment from 'components/Environments/EnvironmentSettings/CreateEnvironment';
import EnvironmentList from './EnvironmentList';
import StyledWrapper from './StyledWrapper';
-import ImportEnvironmentModal from 'components/Environments/Common/ImportEnvironmentModal';
import { IconFileAlert } from '@tabler/icons';
+import ImportEnvironmentModal from 'components/Environments/Common/ImportEnvironmentModal';
import ExportEnvironmentModal from 'components/Environments/Common/ExportEnvironmentModal';
-export const SharedButton = ({ children, className, onClick }) => {
- return (
-
- );
-};
-
-const DefaultTab = ({ setTab }) => {
- return (
-
-
-
No environments found
-
- Get started by using the following buttons :
-
-
- setTab('create')}>
- Create Environment
-
-
- Or
-
- setTab('import')}>
- Import Environment
-
-
+const DefaultTab = ({ setTab }) => (
+
+
+
No Environments
+
+
+
- );
-};
+
+);
-const EnvironmentSettings = ({ collection, onClose }) => {
+const EnvironmentSettings = ({ collection }) => {
const [isModified, setIsModified] = useState(false);
- const { environments } = collection;
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [tab, setTab] = useState('default');
const [showExportModal, setShowExportModal] = useState(false);
+
+ const environments = collection?.environments || [];
+
if (!environments || !environments.length) {
return (
-
- {tab === 'create' ? (
- setTab('default')} />
- ) : tab === 'import' ? (
- setTab('default')} />
- ) : (
-
- )}
-
+ {tab === 'create' ? (
+ setTab('default')} />
+ ) : tab === 'import' ? (
+ setTab('default')} />
+ ) : (
+
+ )}
);
}
return (
-
-
-
+
{showExportModal && (
setShowExportModal(false)}
- environments={collection.environments}
+ environments={environments}
environmentType="collection"
/>
)}
diff --git a/packages/bruno-app/src/components/Environments/GlobalEnvironmentSettings/index.js b/packages/bruno-app/src/components/Environments/GlobalEnvironmentSettings/index.js
new file mode 100644
index 000000000..6a5790a7a
--- /dev/null
+++ b/packages/bruno-app/src/components/Environments/GlobalEnvironmentSettings/index.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import WorkspaceEnvironments from 'components/WorkspaceHome/WorkspaceEnvironments';
+
+const GlobalEnvironmentSettings = () => {
+ return ;
+};
+
+export default GlobalEnvironmentSettings;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/CopyEnvironment/index.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/CopyEnvironment/index.js
deleted file mode 100644
index cb7510c7d..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/CopyEnvironment/index.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import Modal from 'components/Modal/index';
-import Portal from 'components/Portal/index';
-import { useFormik } from 'formik';
-import { copyGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
-import { useEffect, useRef } from 'react';
-import toast from 'react-hot-toast';
-import { useDispatch } from 'react-redux';
-import * as Yup from 'yup';
-
-const CopyEnvironment = ({ environment, onClose }) => {
- const dispatch = useDispatch();
- const inputRef = useRef();
- const formik = useFormik({
- enableReinitialize: true,
- initialValues: {
- name: environment.name + ' - Copy'
- },
- validationSchema: Yup.object({
- name: Yup.string()
- .min(1, 'must be at least 1 character')
- .max(50, 'must be 50 characters or less')
- .required('name is required')
- }),
- onSubmit: (values) => {
- dispatch(copyGlobalEnvironment({ name: values.name, environmentUid: environment.uid }))
- .then(() => {
- toast.success('Global environment created!');
- onClose();
- })
- .catch((error) => {
- toast.error('An error occurred while created the environment');
- console.error(error);
- });
- }
- });
-
- useEffect(() => {
- if (inputRef && inputRef.current) {
- inputRef.current.focus();
- }
- }, [inputRef]);
-
- const onSubmit = () => {
- formik.handleSubmit();
- };
-
- return (
-
-
-
-
-
- );
-};
-
-export default CopyEnvironment;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/CreateEnvironment/index.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/CreateEnvironment/index.js
deleted file mode 100644
index e6156baf1..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/CreateEnvironment/index.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import React, { useEffect, useRef } from 'react';
-import toast from 'react-hot-toast';
-import { useFormik } from 'formik';
-import * as Yup from 'yup';
-import { useDispatch, useSelector } from 'react-redux';
-import Portal from 'components/Portal';
-import Modal from 'components/Modal';
-import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
-import { validateName, validateNameError } from 'utils/common/regex';
-
-const CreateEnvironment = ({ onClose, onEnvironmentCreated }) => {
- const globalEnvs = useSelector((state) => state?.globalEnvironments?.globalEnvironments);
-
- const validateEnvironmentName = (name) => {
- const trimmedName = name?.toLowerCase().trim();
- return globalEnvs.every((env) => env?.name?.toLowerCase().trim() !== trimmedName);
- };
-
- const dispatch = useDispatch();
- const inputRef = useRef();
- const formik = useFormik({
- enableReinitialize: true,
- initialValues: {
- name: ''
- },
- validationSchema: Yup.object({
- name: Yup.string()
- .min(1, 'Must be at least 1 character')
- .max(255, 'Must be 255 characters or less')
- .test('is-valid-filename', function (value) {
- const isValid = validateName(value);
- return isValid ? true : this.createError({ message: validateNameError(value) });
- })
- .required('Name is required')
- .test('duplicate-name', 'Global Environment already exists', validateEnvironmentName)
- }),
- onSubmit: (values) => {
- dispatch(addGlobalEnvironment({ name: values.name }))
- .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'));
- }
- });
-
- useEffect(() => {
- if (inputRef && inputRef.current) {
- inputRef.current.focus();
- }
- }, [inputRef]);
-
- const onSubmit = () => {
- formik.handleSubmit();
- };
-
- return (
-
-
-
-
-
- );
-};
-
-export default CreateEnvironment;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/DeleteEnvironment/StyledWrapper.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/DeleteEnvironment/StyledWrapper.js
deleted file mode 100644
index 48b874214..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/DeleteEnvironment/StyledWrapper.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import styled from 'styled-components';
-
-const Wrapper = styled.div`
- button.submit {
- color: white;
- background-color: var(--color-background-danger) !important;
- border: inherit !important;
-
- &:hover {
- border: inherit !important;
- }
- }
-`;
-
-export default Wrapper;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/DeleteEnvironment/index.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/DeleteEnvironment/index.js
deleted file mode 100644
index ff81e8be2..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/DeleteEnvironment/index.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import Portal from 'components/Portal/index';
-import toast from 'react-hot-toast';
-import Modal from 'components/Modal/index';
-import { useDispatch } from 'react-redux';
-import StyledWrapper from './StyledWrapper';
-import { deleteGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
-
-const DeleteEnvironment = ({ onClose, environment }) => {
- const dispatch = useDispatch();
- const onConfirm = () => {
- dispatch(deleteGlobalEnvironment({ environmentUid: environment.uid }))
- .then(() => {
- toast.success('Global Environment deleted successfully');
- onClose();
- })
- .catch(() => toast.error('An error occurred while deleting the environment'));
- };
-
- return (
-
-
-
- Are you sure you want to delete {environment.name} ?
-
-
-
- );
-};
-
-export default DeleteEnvironment;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/ConfirmSwitchEnv.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/ConfirmSwitchEnv.js
deleted file mode 100644
index 6a587b4fc..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/ConfirmSwitchEnv.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import { IconAlertTriangle } from '@tabler/icons';
-import Modal from 'components/Modal';
-import { createPortal } from 'react-dom';
-
-const ConfirmSwitchEnv = ({ onCancel }) => {
- return createPortal(
- {
- e.stopPropagation();
- e.preventDefault();
- }}
- hideFooter={true}
- >
-
-
-
Hold on..
-
- You have unsaved changes in this environment.
-
-
- ,
- document.body
- );
-};
-
-export default ConfirmSwitchEnv;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
deleted file mode 100644
index 0dc900d13..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import styled from 'styled-components';
-
-const Wrapper = styled.div`
- table {
- width: 100%;
- border-collapse: collapse;
- font-weight: 500;
- table-layout: fixed;
-
- thead,
- td {
- border: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder};
- padding: 4px 10px;
- vertical-align: middle;
-
- &:nth-child(1),
- &:nth-child(4) {
- width: 70px;
- }
- &:nth-child(5) {
- width: 40px;
- }
-
- &:nth-child(2) {
- width: 25%;
- }
- }
-
- thead {
- color: ${(props) => props.theme.table.thead.color};
- font-size: ${(props) => props.theme.font.size.base};
- user-select: none;
- }
- thead td {
- padding: 6px 10px;
- }
- }
-
- .btn-add-param {
- font-size: ${(props) => props.theme.font.size.base};
- }
-
- .tooltip-mod {
- font-size: ${(props) => props.theme.font.size.xs} !important;
- width: 150px !important;
- }
-
- input[type='text'] {
- width: 100%;
- border: solid 1px transparent;
- outline: none !important;
- background-color: transparent;
-
- &:focus {
- outline: none !important;
- border: solid 1px transparent;
- }
- }
-
- input[type='checkbox'] {
- cursor: pointer;
- vertical-align: middle;
- margin: 0;
- }
-`;
-
-export default Wrapper;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
deleted file mode 100644
index 6107bc59c..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
+++ /dev/null
@@ -1,224 +0,0 @@
-import React, { useRef, useEffect } from 'react';
-import cloneDeep from 'lodash/cloneDeep';
-import { IconTrash, IconAlertCircle, IconInfoCircle } from '@tabler/icons';
-import { useTheme } from 'providers/Theme';
-import { useDispatch, useSelector } from 'react-redux';
-import MultiLineEditor from 'components/MultiLineEditor/index';
-import StyledWrapper from './StyledWrapper';
-import { uuid } from 'utils/common';
-import { useFormik } from 'formik';
-import * as Yup from 'yup';
-import { variableNameRegex } from 'utils/common/regex';
-import toast from 'react-hot-toast';
-import { saveGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
-import { Tooltip } from 'react-tooltip';
-import { getGlobalEnvironmentVariables } from 'utils/collections';
-
-const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables, collection }) => {
- const dispatch = useDispatch();
- const { storedTheme } = useTheme();
- const addButtonRef = useRef(null);
- const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
-
- let _collection = cloneDeep(collection);
-
- const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
- _collection.globalEnvironmentVariables = globalEnvironmentVariables;
-
- const formik = useFormik({
- enableReinitialize: true,
- initialValues: environment.variables || [],
- validationSchema: Yup.array().of(
- Yup.object({
- enabled: Yup.boolean(),
- name: Yup.string()
- .required('Name cannot be empty')
- .matches(
- variableNameRegex,
- 'Name contains invalid characters. Must only contain alphanumeric characters, "-", "_", "." and cannot start with a digit.'
- )
- .trim(),
- secret: Yup.boolean(),
- type: Yup.string(),
- uid: Yup.string(),
- value: Yup.mixed().nullable()
- })
- ),
- onSubmit: (values) => {
- if (!formik.dirty) {
- toast.error('Nothing to save');
- return;
- }
-
- dispatch(saveGlobalEnvironment({ environmentUid: environment.uid, variables: cloneDeep(values) }))
- .then(() => {
- toast.success('Changes saved successfully');
- formik.resetForm({ values });
- setIsModified(false);
- })
- .catch((error) => {
- console.error(error);
- toast.error('An error occurred while saving the changes');
- });
- }
- });
-
- // Effect to track modifications.
- React.useEffect(() => {
- setIsModified(formik.dirty);
- }, [formik.dirty]);
-
- const ErrorMessage = ({ name }) => {
- const meta = formik.getFieldMeta(name);
- const id = uuid();
- if (!meta.error || !meta.touched) {
- return null;
- }
- return (
-
-
-
-
- );
- };
-
- const addVariable = () => {
- const newVariable = {
- uid: uuid(),
- name: '',
- value: '',
- type: 'text',
- secret: false,
- enabled: true
- };
- formik.setFieldValue(formik.values.length, newVariable, false);
- };
-
- const handleRemoveVar = (id) => {
- formik.setValues(formik.values.filter((variable) => variable.uid !== id));
- };
-
- useEffect(() => {
- if (formik.dirty) {
- // Smooth scrolling to the changed parameter is temporarily disabled
- // due to UX issues when editing the first row in a long list of environment variables.
- // addButtonRef.current?.scrollIntoView({ behavior: 'smooth' });
- }
- }, [formik.values, formik.dirty]);
-
- const handleReset = () => {
- formik.resetForm({ originalEnvironmentVariables });
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-export default EnvironmentVariables;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
deleted file mode 100644
index d88cb9c65..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import { IconCopy, IconDatabase, IconEdit, IconTrash } from '@tabler/icons';
-import { useState } from 'react';
-import CopyEnvironment from '../../CopyEnvironment';
-import DeleteEnvironment from '../../DeleteEnvironment';
-import RenameEnvironment from '../../RenameEnvironment';
-import EnvironmentVariables from './EnvironmentVariables';
-import ToolHint from 'components/ToolHint/index';
-
-const EnvironmentDetails = ({ environment, setIsModified, collection, allEnvironments }) => {
- const [openEditModal, setOpenEditModal] = useState(false);
- const [openDeleteModal, setOpenDeleteModal] = useState(false);
- const [openCopyModal, setOpenCopyModal] = useState(false);
-
- return (
-
- {openEditModal && (
-
setOpenEditModal(false)} environment={environment} />
- )}
- {openDeleteModal && (
- setOpenDeleteModal(false)}
- environment={environment}
- />
- )}
- {openCopyModal && (
- setOpenCopyModal(false)} environment={environment} />
- )}
-
-
-
- {environment.name}
-
-
-
- setOpenEditModal(true)} />
-
-
- setOpenCopyModal(true)} />
-
-
- setOpenDeleteModal(true)} data-testid="delete-environment-button" />
-
-
-
-
-
-
-
-
- );
-};
-
-export default EnvironmentDetails;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/StyledWrapper.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
deleted file mode 100644
index dd9761532..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/StyledWrapper.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import styled from 'styled-components';
-
-const StyledWrapper = styled.div`
- margin-inline: -1rem;
- margin-block: -1.5rem;
-
- background-color: ${(props) => props.theme.collection.environment.settings.bg};
-
- .environments-sidebar {
- background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
- border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
- min-height: 400px;
- height: 100%;
- max-height: 85vh;
- overflow-y: auto;
- }
-
- .environment-item {
- min-width: 150px;
- display: block;
- position: relative;
- cursor: pointer;
- padding: 8px 10px;
- border-left: solid 2px transparent;
- text-decoration: none;
- max-width: 200px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-
- &:hover {
- text-decoration: none;
- background-color: ${(props) => props.theme.collection.environment.settings.item.hoverBg};
- }
- }
-
- .active {
- background-color: ${(props) => props.theme.collection.environment.settings.item.active.bg} !important;
- border-left: solid 2px ${(props) => props.theme.collection.environment.settings.item.border};
- &:hover {
- background-color: ${(props) => props.theme.collection.environment.settings.item.active.hoverBg} !important;
- }
- }
-
- .btn-create-environment,
- .btn-import-environment {
- padding: 8px 10px;
- cursor: pointer;
- border-bottom: none;
- color: ${(props) => props.theme.textLink};
-
- span:hover {
- text-decoration: underline;
- }
- }
-
- .btn-import-environment {
- color: ${(props) => props.theme.colors.text.muted};
- }
-`;
-
-export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/index.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/index.js
deleted file mode 100644
index c62be278e..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/EnvironmentList/index.js
+++ /dev/null
@@ -1,163 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import usePrevious from 'hooks/usePrevious';
-import EnvironmentDetails from './EnvironmentDetails';
-import CreateEnvironment from '../CreateEnvironment';
-import { IconDownload, IconShieldLock, IconUpload } from '@tabler/icons';
-import StyledWrapper from './StyledWrapper';
-import ConfirmSwitchEnv from './ConfirmSwitchEnv';
-import ManageSecrets from 'components/Environments/EnvironmentSettings/ManageSecrets/index';
-import ImportEnvironmentModal from 'components/Environments/Common/ImportEnvironmentModal';
-import { isEqual } from 'lodash';
-import ToolHint from 'components/ToolHint/index';
-
-const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironment, setSelectedEnvironment, isModified, setIsModified, collection, setShowExportModal }) => {
- const [openCreateModal, setOpenCreateModal] = useState(false);
- const [openImportModal, setOpenImportModal] = useState(false);
- const [openManageSecretsModal, setOpenManageSecretsModal] = useState(false);
-
- const [switchEnvConfirmClose, setSwitchEnvConfirmClose] = useState(false);
- const [originalEnvironmentVariables, setOriginalEnvironmentVariables] = useState([]);
-
- const envUids = environments ? environments.map((env) => env.uid) : [];
- const prevEnvUids = usePrevious(envUids);
-
- useEffect(() => {
- if (!environments?.length) {
- setSelectedEnvironment(null);
- setOriginalEnvironmentVariables([]);
- return;
- }
-
- if (selectedEnvironment) {
- const _selectedEnvironment = environments?.find((env) => env?.uid === selectedEnvironment?.uid);
- const hasSelectedEnvironmentChanged = !isEqual(selectedEnvironment, _selectedEnvironment);
- if (hasSelectedEnvironmentChanged) {
- setSelectedEnvironment(_selectedEnvironment);
- }
- setOriginalEnvironmentVariables(selectedEnvironment.variables);
- return;
- }
-
- const environment = environments?.find((env) => env.uid === activeEnvironmentUid) || environments?.[0] || null;
-
- setSelectedEnvironment(environment);
- setOriginalEnvironmentVariables(environment?.variables || []);
- }, [environments, activeEnvironmentUid]);
-
- useEffect(() => {
- if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
- const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid));
- if (newEnv) {
- setSelectedEnvironment(newEnv);
- }
- }
-
- if (prevEnvUids && prevEnvUids.length && envUids.length < prevEnvUids.length) {
- setSelectedEnvironment(environments && environments.length ? environments[0] : null);
- }
- }, [envUids, environments, prevEnvUids]);
-
- const handleEnvironmentClick = (env) => {
- if (!isModified) {
- setSelectedEnvironment(env);
- } else {
- setSwitchEnvConfirmClose(true);
- }
- };
-
- if (!selectedEnvironment) {
- return null;
- }
-
- const handleCreateEnvClick = () => {
- if (!isModified) {
- setOpenCreateModal(true);
- } else {
- setSwitchEnvConfirmClose(true);
- }
- };
-
- const handleImportClick = () => {
- if (!isModified) {
- setOpenImportModal(true);
- } else {
- setSwitchEnvConfirmClose(true);
- }
- };
-
- const handleSecretsClick = () => {
- setOpenManageSecretsModal(true);
- };
-
- const handleExportClick = () => {
- if (setShowExportModal) {
- setShowExportModal(true);
- }
- };
-
- const handleConfirmSwitch = (saveChanges) => {
- if (!saveChanges) {
- setSwitchEnvConfirmClose(false);
- }
- };
-
- return (
-
- {openCreateModal && setOpenCreateModal(false)} />}
- {openImportModal && setOpenImportModal(false)} />}
- {openManageSecretsModal && setOpenManageSecretsModal(false)} />}
-
-
-
- {switchEnvConfirmClose && (
-
- handleConfirmSwitch(false)} />
-
- )}
-
- {environments
- && environments.length
- && environments.map((env) => (
-
- handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle click
- >
- {env.name}
-
-
- ))}
-
handleCreateEnvClick()}>
- + Create
-
-
-
-
handleImportClick()}>
-
- Import
-
-
handleExportClick()}>
-
- Export
-
-
handleSecretsClick()}>
-
- Managing Secrets
-
-
-
-
-
-
-
- );
-};
-
-export default EnvironmentList;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/RenameEnvironment/index.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/RenameEnvironment/index.js
deleted file mode 100644
index a6193de97..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/RenameEnvironment/index.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import React, { useEffect, useRef } from 'react';
-import Portal from 'components/Portal/index';
-import Modal from 'components/Modal/index';
-import toast from 'react-hot-toast';
-import { useFormik } from 'formik';
-import * as Yup from 'yup';
-import { useDispatch } from 'react-redux';
-import { renameGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
-import { validateName, validateNameError } from 'utils/common/regex';
-
-const RenameEnvironment = ({ onClose, environment }) => {
- const dispatch = useDispatch();
- const inputRef = useRef();
- const formik = useFormik({
- enableReinitialize: true,
- initialValues: {
- name: environment.name
- },
- validationSchema: Yup.object({
- name: Yup.string()
- .min(1, 'must be at least 1 character')
- .max(255, 'Must be 255 characters or less')
- .test('is-valid-filename', function (value) {
- const isValid = validateName(value);
- return isValid ? true : this.createError({ message: validateNameError(value) });
- })
- .required('name is required')
- }),
- onSubmit: (values) => {
- if (values.name === environment.name) {
- return;
- }
- dispatch(renameGlobalEnvironment({ name: values.name, environmentUid: environment.uid }))
- .then(() => {
- toast.success('Environment renamed successfully');
- onClose();
- })
- .catch((error) => {
- toast.error('An error occurred while renaming the environment');
- console.error(error);
- });
- }
- });
-
- useEffect(() => {
- if (inputRef && inputRef.current) {
- inputRef.current.focus();
- }
- }, [inputRef]);
-
- const onSubmit = () => {
- formik.handleSubmit();
- };
-
- return (
-
-
-
-
-
- );
-};
-
-export default RenameEnvironment;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/StyledWrapper.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/StyledWrapper.js
deleted file mode 100644
index 2dfad0cfe..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/StyledWrapper.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import styled from 'styled-components';
-
-const StyledWrapper = styled.div`
- button.btn-create-environment {
- &:hover {
- span {
- text-decoration: underline;
- }
- }
- }
-`;
-
-export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/index.js b/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/index.js
deleted file mode 100644
index 37eace0a9..000000000
--- a/packages/bruno-app/src/components/GlobalEnvironments/EnvironmentSettings/index.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import Modal from 'components/Modal/index';
-import React, { useState } from 'react';
-import CreateEnvironment from './CreateEnvironment';
-import EnvironmentList from './EnvironmentList';
-import StyledWrapper from './StyledWrapper';
-import { IconFileAlert } from '@tabler/icons';
-import ImportEnvironmentModal from 'components/Environments/Common/ImportEnvironmentModal';
-import ExportEnvironmentModal from 'components/Environments/Common/ExportEnvironmentModal';
-
-export const SharedButton = ({ children, className, onClick }) => {
- return (
-
- );
-};
-
-const DefaultTab = ({ setTab }) => {
- return (
-
-
-
No Global Environments found
-
- setTab('create')}>
- Create Global Environment
-
-
- Or
-
- setTab('import')}>
- Import Environment
-
-
-
- );
-};
-
-const EnvironmentSettings = ({ globalEnvironments, collection, activeGlobalEnvironmentUid, onClose }) => {
- const [isModified, setIsModified] = useState(false);
- const environments = globalEnvironments;
- const [selectedEnvironment, setSelectedEnvironment] = useState(null);
- const [tab, setTab] = useState('default');
- const [showExportModal, setShowExportModal] = useState(false);
- if (!environments || !environments.length) {
- return (
-
-
- {tab === 'create' ? (
- setTab('default')} />
- ) : tab === 'import' ? (
- setTab('default')} />
- ) : (
-
- )}
-
-
- );
- }
-
- return (
-
-
-
-
- {showExportModal && (
- setShowExportModal(false)}
- environments={globalEnvironments}
- environmentType="global"
- />
- )}
-
- );
-};
-
-export default EnvironmentSettings;
diff --git a/packages/bruno-app/src/components/RequestTabPanel/index.js b/packages/bruno-app/src/components/RequestTabPanel/index.js
index c438ef9dd..6b4d0b409 100644
--- a/packages/bruno-app/src/components/RequestTabPanel/index.js
+++ b/packages/bruno-app/src/components/RequestTabPanel/index.js
@@ -34,6 +34,8 @@ import WSResponsePane from 'components/ResponsePane/WsResponsePane';
import { useTabPaneBoundaries } from 'hooks/useTabPaneBoundaries/index';
import ResponseExample from 'components/ResponseExample';
import WorkspaceHome from 'components/WorkspaceHome';
+import EnvironmentSettings from 'components/Environments/EnvironmentSettings';
+import GlobalEnvironmentSettings from 'components/Environments/GlobalEnvironmentSettings';
const MIN_LEFT_PANE_WIDTH = 300;
const MIN_RIGHT_PANE_WIDTH = 480;
@@ -147,8 +149,6 @@ const RequestTabPanel = () => {
};
}, [handleMouseUp, handleMouseMove]);
- // When devtools opens in vertical layout, reduce request pane height to ensure response pane is visible
- // When devtools closes, restore the previous height
useEffect(() => {
if (!isVerticalLayout) return;
@@ -171,11 +171,15 @@ const RequestTabPanel = () => {
}
}, [isConsoleOpen, isVerticalLayout]);
- if (!activeTabUid) {
+ if (!activeTabUid || !focusedTab) {
return ;
}
- if (!focusedTab || !focusedTab.uid || !focusedTab.collectionUid) {
+ if (focusedTab.type === 'global-environment-settings') {
+ return ;
+ }
+
+ if (!focusedTab.uid || !focusedTab.collectionUid) {
return An error occurred!
;
}
@@ -226,6 +230,10 @@ const RequestTabPanel = () => {
return ;
}
+ if (focusedTab.type === 'environment-settings') {
+ return ;
+ }
+
if (!item || !item.uid) {
return ;
}
diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js
index 0ea63e4fb..a85fd4ea4 100644
--- a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js
+++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js
@@ -1,6 +1,6 @@
import React from 'react';
import GradientCloseButton from './GradientCloseButton';
-import { IconVariable, IconSettings, IconRun, IconFolder, IconShieldLock } from '@tabler/icons';
+import { IconVariable, IconSettings, IconRun, IconFolder, IconShieldLock, IconDatabase, IconWorld } from '@tabler/icons';
const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDraft }) => {
const getTabInfo = (type, tabName) => {
@@ -53,6 +53,22 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
>
);
}
+ case 'environment-settings': {
+ return (
+ <>
+
+ Environments
+ >
+ );
+ }
+ case 'global-environment-settings': {
+ return (
+ <>
+
+ Global Environments
+ >
+ );
+ }
}
};
diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
index 18633c2da..8998ca9cc 100644
--- a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
+++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
@@ -1,16 +1,19 @@
import React, { useCallback, useState, useRef, Fragment, useMemo, useEffect } from 'react';
import get from 'lodash/get';
import { closeTabs, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
-import { saveRequest, saveCollectionRoot, saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
-import { deleteRequestDraft, deleteCollectionDraft, deleteFolderDraft } from 'providers/ReduxStore/slices/collections';
+import { saveRequest, saveCollectionRoot, saveFolderRoot, saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
+import { deleteRequestDraft, deleteCollectionDraft, deleteFolderDraft, clearEnvironmentsDraft } from 'providers/ReduxStore/slices/collections';
+import { clearGlobalEnvironmentDraft } from 'providers/ReduxStore/slices/global-environments';
+import { saveGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
import { useTheme } from 'providers/Theme';
-import { useDispatch } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import darkTheme from 'themes/dark';
import lightTheme from 'themes/light';
import { findItemInCollection, hasRequestChanges } from 'utils/collections';
import ConfirmRequestClose from './ConfirmRequestClose';
import ConfirmCollectionClose from './ConfirmCollectionClose';
import ConfirmFolderClose from './ConfirmFolderClose';
+import ConfirmCloseEnvironment from 'components/Environments/ConfirmCloseEnvironment';
import RequestTabNotFound from './RequestTabNotFound';
import SpecialTab from './SpecialTab';
import StyledWrapper from './StyledWrapper';
@@ -21,6 +24,7 @@ import GradientCloseButton from './GradientCloseButton';
import { flattenItems } from 'utils/collections/index';
import { closeWsConnection } from 'utils/network/index';
import ExampleTab from '../ExampleTab';
+import toast from 'react-hot-toast';
const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid, hasOverflow, setHasOverflow }) => {
const dispatch = useDispatch();
@@ -31,6 +35,8 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
const [showConfirmClose, setShowConfirmClose] = useState(false);
const [showConfirmCollectionClose, setShowConfirmCollectionClose] = useState(false);
const [showConfirmFolderClose, setShowConfirmFolderClose] = useState(false);
+ const [showConfirmEnvironmentClose, setShowConfirmEnvironmentClose] = useState(false);
+ const [showConfirmGlobalEnvironmentClose, setShowConfirmGlobalEnvironmentClose] = useState(false);
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
@@ -152,8 +158,31 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
const hasDraft = tab.type === 'collection-settings' && collection?.draft;
const hasFolderDraft = tab.type === 'folder-settings' && folder?.draft;
+ const hasEnvironmentDraft = tab.type === 'environment-settings' && collection?.environmentsDraft;
+ const globalEnvironmentDraft = useSelector((state) => state.globalEnvironments.globalEnvironmentDraft);
+ const hasGlobalEnvironmentDraft = tab.type === 'global-environment-settings' && globalEnvironmentDraft;
- if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
+ const handleCloseEnvironmentSettings = (event) => {
+ if (!collection?.environmentsDraft) {
+ return handleCloseClick(event);
+ }
+
+ event.stopPropagation();
+ event.preventDefault();
+ setShowConfirmEnvironmentClose(true);
+ };
+
+ const handleCloseGlobalEnvironmentSettings = (event) => {
+ if (!globalEnvironmentDraft) {
+ return handleCloseClick(event);
+ }
+
+ event.stopPropagation();
+ event.preventDefault();
+ setShowConfirmGlobalEnvironmentClose(true);
+ };
+
+ if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'security-settings', 'environment-settings', 'global-environment-settings'].includes(tab.type)) {
return (
)}
+ {showConfirmEnvironmentClose && tab.type === 'environment-settings' && (
+ setShowConfirmEnvironmentClose(false)}
+ onCloseWithoutSave={() => {
+ dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
+ dispatch(closeTabs({ tabUids: [tab.uid] }));
+ setShowConfirmEnvironmentClose(false);
+ }}
+ onSaveAndClose={() => {
+ const draft = collection.environmentsDraft;
+ if (draft?.environmentUid && draft?.variables) {
+ dispatch(saveEnvironment(draft.variables, draft.environmentUid, collection.uid))
+ .then(() => {
+ dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
+ dispatch(closeTabs({ tabUids: [tab.uid] }));
+ setShowConfirmEnvironmentClose(false);
+ toast.success('Environment saved');
+ })
+ .catch((err) => {
+ console.log('err', err);
+ toast.error('Failed to save environment');
+ });
+ }
+ }}
+ />
+ )}
+ {showConfirmGlobalEnvironmentClose && tab.type === 'global-environment-settings' && (
+ setShowConfirmGlobalEnvironmentClose(false)}
+ onCloseWithoutSave={() => {
+ dispatch(clearGlobalEnvironmentDraft());
+ dispatch(closeTabs({ tabUids: [tab.uid] }));
+ setShowConfirmGlobalEnvironmentClose(false);
+ }}
+ onSaveAndClose={() => {
+ const draft = globalEnvironmentDraft;
+ if (draft?.environmentUid && draft?.variables) {
+ dispatch(saveGlobalEnvironment({ variables: draft.variables, environmentUid: draft.environmentUid }))
+ .then(() => {
+ dispatch(clearGlobalEnvironmentDraft());
+ dispatch(closeTabs({ tabUids: [tab.uid] }));
+ setShowConfirmGlobalEnvironmentClose(false);
+ toast.success('Global environment saved');
+ })
+ .catch((err) => {
+ console.log('err', err);
+ toast.error('Failed to save global environment');
+ });
+ }
+ }}
+ />
+ )}
{tab.type === 'folder-settings' && !folder ? (
) : tab.type === 'folder-settings' ? (
dispatch(makeTabPermanent({ uid: tab.uid }))} type={tab.type} tabName={folder?.name} hasDraft={hasFolderDraft} />
) : tab.type === 'collection-settings' ? (
dispatch(makeTabPermanent({ uid: tab.uid }))} type={tab.type} tabName={collection?.name} hasDraft={hasDraft} />
+ ) : tab.type === 'environment-settings' ? (
+ dispatch(makeTabPermanent({ uid: tab.uid }))} type={tab.type} hasDraft={hasEnvironmentDraft} />
+ ) : tab.type === 'global-environment-settings' ? (
+ dispatch(makeTabPermanent({ uid: tab.uid }))} type={tab.type} hasDraft={hasGlobalEnvironmentDraft} />
) : (
dispatch(makeTabPermanent({ uid: tab.uid }))} type={tab.type} />
)}
diff --git a/packages/bruno-app/src/components/RequestTabs/index.js b/packages/bruno-app/src/components/RequestTabs/index.js
index 73f34b007..647d0cb69 100644
--- a/packages/bruno-app/src/components/RequestTabs/index.js
+++ b/packages/bruno-app/src/components/RequestTabs/index.js
@@ -83,10 +83,6 @@ const RequestTabs = () => {
return null;
}
- if (!activeTab) {
- return Something went wrong!;
- }
-
const effectiveSidebarWidth = sidebarCollapsed ? 0 : leftSidebarWidth;
const maxTablistWidth = screenWidth - effectiveSidebarWidth - 150;
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js
index a286108ad..e199de25e 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js
@@ -32,12 +32,12 @@ const CreateEnvironment = ({ onClose, onEnvironmentCreated }) => {
return isValid ? true : this.createError({ message: validateNameError(value) });
})
.required('Name is required')
- .test('duplicate-name', 'Environment already exists', validateEnvironmentName)
+ .test('duplicate-name', 'Global environment already exists', validateEnvironmentName)
}),
onSubmit: (values) => {
dispatch(addGlobalEnvironment({ name: values.name }))
.then(() => {
- toast.success('Environment created!');
+ toast.success('Global environment created!');
onClose();
// Call the callback if provided
if (onEnvironmentCreated) {
@@ -62,7 +62,7 @@ const CreateEnvironment = ({ onClose, onEnvironmentCreated }) => {
props.theme.bg};
flex-shrink: 0;
display: flex;
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
index 515d9f1fc..a93424133 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useCallback, useRef } from 'react';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash, IconAlertCircle, IconInfoCircle } from '@tabler/icons';
import { useTheme } from 'providers/Theme';
@@ -10,14 +10,26 @@ import { useFormik } from 'formik';
import * as Yup from 'yup';
import { variableNameRegex } from 'utils/common/regex';
import toast from 'react-hot-toast';
-import { saveGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
+import {
+ saveGlobalEnvironment,
+ setGlobalEnvironmentDraft,
+ clearGlobalEnvironmentDraft
+} from 'providers/ReduxStore/slices/global-environments';
import { Tooltip } from 'react-tooltip';
import { getGlobalEnvironmentVariables } from 'utils/collections';
const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
- const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
+ const { globalEnvironments, activeGlobalEnvironmentUid, globalEnvironmentDraft } = useSelector(
+ (state) => state.globalEnvironments
+ );
+
+ const hasDraftForThisEnv = globalEnvironmentDraft?.environmentUid === environment.uid;
+
+ // Track environment changes for draft restoration
+ const prevEnvUidRef = React.useRef(null);
+ const mountedRef = React.useRef(false);
let _collection = collection ? cloneDeep(collection) : {};
@@ -26,6 +38,8 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
_collection.globalEnvironmentVariables = globalEnvironmentVariables;
}
+ // Initial values based only on saved environment variables (not draft)
+ // Draft restoration happens in a separate effect to avoid infinite loops
const initialValues = React.useMemo(() => {
const vars = environment.variables || [];
return [
@@ -67,12 +81,10 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
const isLastRow = index === values.length - 1;
const isEmptyRow = !variable.name || variable.name.trim() === '';
- // Skip validation for the last empty row
if (isLastRow && isEmptyRow) {
return;
}
- // Validate name for non-empty rows
if (!variable.name || variable.name.trim() === '') {
if (!errors[index]) errors[index] = {};
errors[index].name = 'Name cannot be empty';
@@ -86,20 +98,61 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
onSubmit: () => {}
});
+ // Restore draft values on mount or environment switch
+ React.useEffect(() => {
+ const isMount = !mountedRef.current;
+ const envChanged = prevEnvUidRef.current !== null && prevEnvUidRef.current !== environment.uid;
+
+ prevEnvUidRef.current = environment.uid;
+ mountedRef.current = true;
+
+ if ((isMount || envChanged) && hasDraftForThisEnv && globalEnvironmentDraft?.variables) {
+ formik.setValues([
+ ...globalEnvironmentDraft.variables,
+ {
+ uid: uuid(),
+ name: '',
+ value: '',
+ type: 'text',
+ secret: false,
+ enabled: true
+ }
+ ]);
+ }
+ }, [environment.uid, hasDraftForThisEnv, globalEnvironmentDraft?.variables]);
+
+ // Sync draft state to Redux
React.useEffect(() => {
const currentValues = formik.values.filter((variable) => variable.name && variable.name.trim() !== '');
const savedValues = environment.variables || [];
- const hasActualChanges = JSON.stringify(currentValues) !== JSON.stringify(savedValues);
+ const currentValuesJson = JSON.stringify(currentValues);
+ const savedValuesJson = JSON.stringify(savedValues);
+ const hasActualChanges = currentValuesJson !== savedValuesJson;
setIsModified(hasActualChanges);
- }, [formik.values, environment.variables, setIsModified]);
+
+ // Get existing draft for comparison
+ const existingDraftVariables = hasDraftForThisEnv ? globalEnvironmentDraft?.variables : null;
+ const existingDraftJson = existingDraftVariables ? JSON.stringify(existingDraftVariables) : null;
+
+ if (hasActualChanges) {
+ // Only dispatch if draft values are actually different
+ if (currentValuesJson !== existingDraftJson) {
+ dispatch(setGlobalEnvironmentDraft({
+ environmentUid: environment.uid,
+ variables: currentValues
+ }));
+ }
+ } else if (hasDraftForThisEnv) {
+ dispatch(clearGlobalEnvironmentDraft());
+ }
+ }, [formik.values, environment.variables, environment.uid, setIsModified, dispatch, hasDraftForThisEnv, globalEnvironmentDraft?.variables]);
const ErrorMessage = ({ name, index }) => {
const meta = formik.getFieldMeta(name);
const id = `error-${name}-${index}`;
- // Don't show error for the last empty row
const isLastRow = index === formik.values.length - 1;
const variable = formik.values[index];
const isEmptyRow = !variable?.name || variable.name.trim() === '';
@@ -119,39 +172,47 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
);
};
- const handleRemoveVar = (id) => {
- const filteredValues = formik.values.filter((variable) => variable.uid !== id);
+ const handleRemoveVar = useCallback((id) => {
+ const currentValues = formik.values;
- const lastRow = formik.values[formik.values.length - 1];
- const isLastEmptyRow = lastRow.uid === id && (!lastRow.name || lastRow.name.trim() === '');
+ if (!currentValues || currentValues.length === 0) {
+ return;
+ }
+
+ const lastRow = currentValues[currentValues.length - 1];
+ const isLastEmptyRow = lastRow?.uid === id && (!lastRow.name || lastRow.name.trim() === '');
if (isLastEmptyRow) {
return;
}
+ const filteredValues = currentValues.filter((variable) => variable.uid !== id);
+
const hasEmptyLastRow = filteredValues.length > 0
&& (!filteredValues[filteredValues.length - 1].name
|| filteredValues[filteredValues.length - 1].name.trim() === '');
- if (!hasEmptyLastRow) {
- filteredValues.push({
- uid: uuid(),
- name: '',
- value: '',
- type: 'text',
- secret: false,
- enabled: true
- });
- }
+ const newValues = hasEmptyLastRow
+ ? filteredValues
+ : [
+ ...filteredValues,
+ {
+ uid: uuid(),
+ name: '',
+ value: '',
+ type: 'text',
+ secret: false,
+ enabled: true
+ }
+ ];
- formik.setValues(filteredValues);
- };
+ formik.setValues(newValues);
+ }, [formik.values]);
const handleNameChange = (index, e) => {
formik.handleChange(e);
const isLastRow = index === formik.values.length - 1;
- // If typing in the last row, add a new empty row
if (isLastRow) {
const newVariable = {
uid: uuid(),
@@ -161,15 +222,32 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
secret: false,
enabled: true
};
- // Use setTimeout to ensure the change is processed first
setTimeout(() => {
formik.setFieldValue(formik.values.length, newVariable, false);
}, 0);
}
};
+ const handleNameBlur = (index) => {
+ formik.setFieldTouched(`${index}.name`, true, true);
+ };
+
+ const handleNameKeyDown = (index, e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ formik.setFieldTouched(`${index}.name`, true, true);
+ }
+ };
+
const handleSave = () => {
const variablesToSave = formik.values.filter((variable) => variable.name && variable.name.trim() !== '');
+ const savedValues = environment.variables || [];
+
+ const hasChanges = JSON.stringify(variablesToSave) !== JSON.stringify(savedValues);
+ if (!hasChanges) {
+ toast.error('No changes to save');
+ return;
+ }
const hasValidationErrors = variablesToSave.some((variable) => {
if (!variable.name || variable.name.trim() === '') {
@@ -223,8 +301,24 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
}
];
formik.resetForm({ values: resetValues });
+ setIsModified(false);
};
+ const handleSaveRef = useRef(handleSave);
+ handleSaveRef.current = handleSave;
+
+ React.useEffect(() => {
+ const handleSaveEvent = () => {
+ handleSaveRef.current();
+ };
+
+ window.addEventListener('environment-save', handleSaveEvent);
+
+ return () => {
+ window.removeEventListener('environment-save', handleSaveEvent);
+ };
+ }, []);
+
return (
@@ -271,6 +365,8 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
value={variable.name}
placeholder={isLastEmptyRow ? 'Name' : ''}
onChange={(e) => handleNameChange(index, e)}
+ onBlur={() => handleNameBlur(index)}
+ onKeyDown={(e) => handleNameKeyDown(index, e)}
/>
@@ -286,6 +382,7 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
isSecret={variable.secret}
readOnly={typeof variable.value !== 'string'}
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
+ onSave={handleSave}
/>
{typeof variable.value !== 'string' && (
@@ -341,4 +438,5 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
);
};
+
export default EnvironmentVariables;
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js
index d4b26793d..6cb5e3ed9 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/EnvironmentDetails/StyledWrapper.js
@@ -81,7 +81,7 @@ const StyledWrapper = styled.div`
.title-error {
position: absolute;
top: 100%;
- left: 0;
+ left: 20px;
margin-top: 4px;
padding: 4px 8px;
font-size: 11px;
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js
index 0f60f349a..750863fb3 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/StyledWrapper.js
@@ -3,6 +3,7 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
display: flex;
height: 100%;
+ overflow: hidden;
background-color: ${(props) => props.theme.bg};
position: relative;
@@ -10,6 +11,7 @@ const StyledWrapper = styled.div`
display: flex;
height: 100%;
width: 100%;
+ overflow: hidden;
}
.confirm-switch-overlay {
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/StyledWrapper.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/StyledWrapper.js
index 7931f4e94..95e8137a1 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/StyledWrapper.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/StyledWrapper.js
@@ -4,6 +4,7 @@ const StyledWrapper = styled.div`
height: 100%;
display: flex;
flex-direction: column;
+ overflow: hidden;
background-color: ${(props) => props.theme.bg};
.empty-state {
diff --git a/packages/bruno-app/src/components/WorkspaceHome/index.js b/packages/bruno-app/src/components/WorkspaceHome/index.js
index 5479e1c17..d28a2c4ab 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/index.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/index.js
@@ -145,7 +145,7 @@ const WorkspaceHome = () => {
const tabs = [
{ id: 'overview', label: 'Overview' },
- { id: 'environments', label: 'Environments' }
+ { id: 'environments', label: 'Global Environments' }
];
const renderTabContent = () => {
diff --git a/packages/bruno-app/src/providers/Hotkeys/index.js b/packages/bruno-app/src/providers/Hotkeys/index.js
index 32aa8be21..e53927833 100644
--- a/packages/bruno-app/src/providers/Hotkeys/index.js
+++ b/packages/bruno-app/src/providers/Hotkeys/index.js
@@ -3,7 +3,6 @@ import toast from 'react-hot-toast';
import find from 'lodash/find';
import Mousetrap from 'mousetrap';
import { useSelector, useDispatch } from 'react-redux';
-import EnvironmentSettings from 'components/Environments/EnvironmentSettings';
import NetworkError from 'components/ResponsePane/NetworkError';
import NewRequest from 'components/Sidebar/NewRequest';
import GlobalSearchModal from 'components/GlobalSearchModal';
@@ -15,7 +14,7 @@ import {
saveCollectionSettings
} from 'providers/ReduxStore/slices/collections/actions';
import { findCollectionByUid, findItemInCollection } from 'utils/collections';
-import { closeTabs, reorderTabs, switchTab } from 'providers/ReduxStore/slices/tabs';
+import { addTab, closeTabs, reorderTabs, switchTab } from 'providers/ReduxStore/slices/tabs';
import { toggleSidebarCollapse } from 'providers/ReduxStore/slices/app';
import { getKeyBindingsForActionAllOS } from './keyMappings';
@@ -26,9 +25,6 @@ export const HotkeysProvider = (props) => {
const tabs = useSelector((state) => state.tabs.tabs);
const collections = useSelector((state) => state.collections.collections);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
- const isEnvironmentSettingsModalOpen = useSelector((state) => state.app.isEnvironmentSettingsModalOpen);
- const isGlobalEnvironmentSettingsModalOpen = useSelector((state) => state.app.isGlobalEnvironmentSettingsModalOpen);
- const [showEnvSettingsModal, setShowEnvSettingsModal] = useState(false);
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
const [showGlobalSearchModal, setShowGlobalSearchModal] = useState(false);
@@ -44,23 +40,24 @@ export const HotkeysProvider = (props) => {
// save hotkey
useEffect(() => {
Mousetrap.bind([...getKeyBindingsForActionAllOS('save')], (e) => {
- if (isEnvironmentSettingsModalOpen || isGlobalEnvironmentSettingsModalOpen) {
- console.log('todo: save environment settings');
- } else {
- const activeTab = find(tabs, (t) => t.uid === activeTabUid);
- if (activeTab) {
- const collection = findCollectionByUid(collections, activeTab.collectionUid);
- if (collection) {
- const item = findItemInCollection(collection, activeTab.uid);
- if (item && item.uid) {
- if (activeTab.type === 'folder-settings') {
- dispatch(saveFolderRoot(collection.uid, item.uid));
- } else {
- dispatch(saveRequest(activeTab.uid, activeTab.collectionUid));
- }
- } else if (activeTab.type === 'collection-settings') {
- dispatch(saveCollectionSettings(collection.uid));
+ const activeTab = find(tabs, (t) => t.uid === activeTabUid);
+ if (activeTab) {
+ if (activeTab.type === 'environment-settings' || activeTab.type === 'global-environment-settings') {
+ window.dispatchEvent(new CustomEvent('environment-save'));
+ return false;
+ }
+
+ const collection = findCollectionByUid(collections, activeTab.collectionUid);
+ if (collection) {
+ const item = findItemInCollection(collection, activeTab.uid);
+ if (item && item.uid) {
+ if (activeTab.type === 'folder-settings') {
+ dispatch(saveFolderRoot(collection.uid, item.uid));
+ } else {
+ dispatch(saveRequest(activeTab.uid, activeTab.collectionUid));
}
+ } else if (activeTab.type === 'collection-settings') {
+ dispatch(saveCollectionSettings(collection.uid));
}
}
}
@@ -71,7 +68,7 @@ export const HotkeysProvider = (props) => {
return () => {
Mousetrap.unbind([...getKeyBindingsForActionAllOS('save')]);
};
- }, [activeTabUid, tabs, saveRequest, collections, isEnvironmentSettingsModalOpen, isGlobalEnvironmentSettingsModalOpen]);
+ }, [activeTabUid, tabs, saveRequest, collections, dispatch]);
// send request (ctrl/cmd + enter)
useEffect(() => {
@@ -120,7 +117,13 @@ export const HotkeysProvider = (props) => {
const collection = findCollectionByUid(collections, activeTab.collectionUid);
if (collection) {
- setShowEnvSettingsModal(true);
+ dispatch(
+ addTab({
+ uid: `${collection.uid}-environment-settings`,
+ collectionUid: collection.uid,
+ type: 'environment-settings'
+ })
+ );
}
}
@@ -130,7 +133,7 @@ export const HotkeysProvider = (props) => {
return () => {
Mousetrap.unbind([...getKeyBindingsForActionAllOS('editEnvironment')]);
};
- }, [activeTabUid, tabs, collections, setShowEnvSettingsModal]);
+ }, [activeTabUid, tabs, collections, dispatch]);
// new request (ctrl/cmd + b)
useEffect(() => {
@@ -281,9 +284,6 @@ export const HotkeysProvider = (props) => {
return (
- {showEnvSettingsModal && (
- setShowEnvSettingsModal(false)} />
- )}
{showNewRequestModal && (
setShowNewRequestModal(false)} />
)}
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/app.js b/packages/bruno-app/src/providers/ReduxStore/slices/app.js
index 045143a08..f43e27a00 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/app.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/app.js
@@ -69,12 +69,6 @@ export const appSlice = createSlice({
updateIsDragging: (state, action) => {
state.isDragging = action.payload.isDragging;
},
- updateEnvironmentSettingsModalVisibility: (state, action) => {
- state.isEnvironmentSettingsModalOpen = action.payload;
- },
- updateGlobalEnvironmentSettingsModalVisibility: (state, action) => {
- state.isGlobalEnvironmentSettingsModalOpen = action.payload;
- },
showHomePage: (state) => {
state.showHomePage = true;
state.showApiSpecPage = false;
@@ -141,8 +135,6 @@ export const {
refreshScreenWidth,
updateLeftSidebarWidth,
updateIsDragging,
- updateEnvironmentSettingsModalVisibility,
- updateGlobalEnvironmentSettingsModalVisibility,
showHomePage,
hideHomePage,
showManageWorkspacePage,
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
index 8dcfb3f56..f07d17f28 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -694,6 +694,19 @@ export const collectionsSlice = createSlice({
folder.draft = null;
}
},
+ setEnvironmentsDraft: (state, action) => {
+ const { collectionUid, environmentUid, variables } = action.payload;
+ const collection = findCollectionByUid(state.collections, collectionUid);
+ if (collection) {
+ collection.environmentsDraft = { environmentUid, variables };
+ }
+ },
+ clearEnvironmentsDraft: (state, action) => {
+ const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
+ if (collection) {
+ collection.environmentsDraft = null;
+ }
+ },
newEphemeralHttpRequest: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@@ -3388,6 +3401,8 @@ export const {
saveFolderDraft,
deleteCollectionDraft,
deleteFolderDraft,
+ setEnvironmentsDraft,
+ clearEnvironmentsDraft,
newEphemeralHttpRequest,
collapseFullCollection,
toggleCollection,
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js b/packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
index 0e5503593..028c0db29 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/global-environments.js
@@ -5,7 +5,8 @@ import { cloneDeep, has } from 'lodash';
const initialState = {
globalEnvironments: [],
- activeGlobalEnvironmentUid: null
+ activeGlobalEnvironmentUid: null,
+ globalEnvironmentDraft: null
};
export const globalEnvironmentsSlice = createSlice({
@@ -73,6 +74,13 @@ export const globalEnvironmentsSlice = createSlice({
state.activeGlobalEnvironmentUid = null;
}
}
+ },
+ setGlobalEnvironmentDraft: (state, action) => {
+ const { environmentUid, variables } = action.payload;
+ state.globalEnvironmentDraft = { environmentUid, variables };
+ },
+ clearGlobalEnvironmentDraft: (state) => {
+ state.globalEnvironmentDraft = null;
}
}
});
@@ -84,7 +92,9 @@ export const {
_renameGlobalEnvironment,
_copyGlobalEnvironment,
_selectGlobalEnvironment,
- _deleteGlobalEnvironment
+ _deleteGlobalEnvironment,
+ setGlobalEnvironmentDraft,
+ clearGlobalEnvironmentDraft
} = globalEnvironmentsSlice.actions;
const getWorkspaceContext = (state) => {
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
index a6ffc9e0f..fde898715 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
@@ -24,7 +24,9 @@ export const tabsSlice = createSlice({
const nonReplaceableTabTypes = [
'variables',
'collection-runner',
- 'security-settings'
+ 'security-settings',
+ 'environment-settings',
+ 'global-environment-settings'
];
const existingTab = find(state.tabs, (tab) => tab.uid === uid);
diff --git a/packages/bruno-app/src/utils/common/regex.js b/packages/bruno-app/src/utils/common/regex.js
index 8e7c796b5..16479cb4a 100644
--- a/packages/bruno-app/src/utils/common/regex.js
+++ b/packages/bruno-app/src/utils/common/regex.js
@@ -39,17 +39,17 @@ export const validateNameError = (name) => {
}
if (!firstCharacter.test(name[0])) {
- return 'Invalid first character.';
+ return `Special characters aren't allowed in the name. Invalid character '${name[0]}'.`;
}
for (let i = 1; i < name.length - 1; i++) {
if (!middleCharacters.test(name[i])) {
- return `Invalid character '${name[i]}' at position ${i + 1}.`;
+ return `Special characters aren't allowed in the name. Invalid character '${name[i]}'.`;
}
}
if (!lastCharacter.test(name[name.length - 1])) {
- return 'Invalid last character.';
+ return `Special characters aren't allowed in the name. Invalid character '${name[name.length - 1]}'.`;
}
return '';
diff --git a/tests/environments/api-setEnvVar/api-setEnvVar-with-persist.spec.ts b/tests/environments/api-setEnvVar/api-setEnvVar-with-persist.spec.ts
index 04a796616..f1e424695 100644
--- a/tests/environments/api-setEnvVar/api-setEnvVar-with-persist.spec.ts
+++ b/tests/environments/api-setEnvVar/api-setEnvVar-with-persist.spec.ts
@@ -28,9 +28,14 @@ test.describe.serial('bru.setEnvVar(name, value, { persist: true })', () => {
await page.getByTestId('environment-selector-trigger').click();
// open environment configuration
await page.locator('#configure-env').click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
+
await expect(page.getByRole('row', { name: 'token' }).getByRole('cell').nth(1)).toBeVisible();
await expect(page.getByRole('row', { name: 'secret' }).getByRole('cell').nth(2)).toBeVisible();
- await page.getByTestId('modal-close-button').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
// we restart the app to confirm that the environment variable is persisted
const newApp = await restartApp();
@@ -43,11 +48,15 @@ test.describe.serial('bru.setEnvVar(name, value, { persist: true })', () => {
// open environment dropdown
await newPage.getByTestId('environment-selector-trigger').click();
await newPage.locator('#configure-env').click();
+
+ const newEnvTab = newPage.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(newEnvTab).toBeVisible();
+
await expect(newPage.getByRole('row', { name: 'token' }).getByRole('cell').nth(1)).toBeVisible();
await expect(newPage.getByRole('row', { name: 'secret' }).getByRole('cell').nth(2)).toBeVisible();
- // close the environment modal
- await newPage.getByTestId('modal-close-button').click();
+ await newEnvTab.hover();
+ await newEnvTab.getByTestId('request-tab-close-icon').click();
// Restore the original Stage.bru file
fs.writeFileSync(originalStageBruPath, originalStageBruContent);
diff --git a/tests/environments/api-setEnvVar/api-setEnvVar-without-persist.spec.ts b/tests/environments/api-setEnvVar/api-setEnvVar-without-persist.spec.ts
index 2972a738f..33ab045db 100644
--- a/tests/environments/api-setEnvVar/api-setEnvVar-without-persist.spec.ts
+++ b/tests/environments/api-setEnvVar/api-setEnvVar-without-persist.spec.ts
@@ -21,9 +21,14 @@ test.describe.serial('bru.setEnvVar(name, value)', () => {
// confirm that the environment variable is set
await page.getByTestId('environment-selector-trigger').click();
await page.locator('#configure-env').click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
+
await expect(page.getByRole('row', { name: 'token' }).getByRole('cell').nth(1)).toBeVisible();
await expect(page.getByRole('row', { name: 'secret' }).getByRole('cell').nth(2)).toBeVisible();
- await page.getByTestId('modal-close-button').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
// we restart the app to confirm that the environment variable is not persisted
const newApp = await restartApp();
@@ -37,11 +42,13 @@ test.describe.serial('bru.setEnvVar(name, value)', () => {
await newPage.getByTestId('environment-selector-trigger').click();
await newPage.locator('#configure-env').click();
- // ensure that the environment variable is not persisted
- await expect(newPage.locator('table.environment-variables tbody')).not.toContainText('token');
+ const newEnvTab = newPage.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(newEnvTab).toBeVisible();
- // close the environment variable modal
- await newPage.getByTestId('modal-close-button').click();
+ await expect(newPage.locator('.table-container tbody')).not.toContainText('token');
+
+ await newEnvTab.hover();
+ await newEnvTab.getByTestId('request-tab-close-icon').click();
await newPage.close();
});
});
diff --git a/tests/environments/api-setEnvVar/multiple-persist-vars.spec.ts b/tests/environments/api-setEnvVar/multiple-persist-vars.spec.ts
index 083b08fc2..b4454e7cb 100644
--- a/tests/environments/api-setEnvVar/multiple-persist-vars.spec.ts
+++ b/tests/environments/api-setEnvVar/multiple-persist-vars.spec.ts
@@ -14,18 +14,20 @@ test.describe.serial('bru.setEnvVar multiple persistent variables', () => {
await page.locator('#configure-env').click();
await page.waitForTimeout(200);
- // Remove the test environment variables
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+
const key1Row = page.getByRole('row', { name: 'multiple-persist-vars-key1' });
if (await key1Row.isVisible()) {
- await key1Row.getByRole('button').click(); // Click the delete button
+ await key1Row.getByRole('button').click();
}
const key2Row = page.getByRole('row', { name: 'multiple-persist-vars-key2' });
if (await key2Row.isVisible()) {
- await key2Row.getByRole('button').click(); // Click the delete button
+ await key2Row.getByRole('button').click();
}
- await page.getByTestId('modal-close-button').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
}
} catch (error) {
// Ignore cleanup errors to avoid masking test failures
@@ -74,11 +76,16 @@ test.describe.serial('bru.setEnvVar multiple persistent variables', () => {
await page.waitForTimeout(200);
await page.locator('#configure-env').click();
await page.waitForTimeout(200);
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
+
await expect(page.getByRole('row', { name: 'multiple-persist-vars-key1' }).getByRole('cell').nth(1)).toBeVisible();
await expect(page.getByRole('row', { name: 'value1' }).getByRole('cell').nth(2)).toBeVisible();
await expect(page.getByRole('row', { name: 'multiple-persist-vars-key2' }).getByRole('cell').nth(1)).toBeVisible();
await expect(page.getByRole('row', { name: 'value2' }).getByRole('cell').nth(2)).toBeVisible();
- await page.getByTestId('modal-close-button').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
await test.step('Verify variables are persisted to file', async () => {
diff --git a/tests/environments/collection-env-config-selection/collection-env-config-selection.spec.ts b/tests/environments/collection-env-config-selection/collection-env-config-selection.spec.ts
index 521cdb483..42948cc77 100644
--- a/tests/environments/collection-env-config-selection/collection-env-config-selection.spec.ts
+++ b/tests/environments/collection-env-config-selection/collection-env-config-selection.spec.ts
@@ -28,15 +28,13 @@ test.describe('Collection Environment Configuration Selection Tests', () => {
await page.getByTestId('env-tab-collection').click();
await page.getByText('Configure', { exact: true }).click();
- // Verify the config modal opens with the currently active environment selected
- const collectionEnvModal = page.locator('.bruno-modal').filter({ hasText: 'Environments' });
- await expect(collectionEnvModal).toBeVisible();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
- // Check that the active environment in the config matches prod
- const activeEnvItem = collectionEnvModal.locator('.environment-item.active');
+ const activeEnvItem = page.locator('.environment-item.active');
await expect(activeEnvItem).toContainText('prod');
- // Close the collection environment config modal
- await page.getByText('×').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
});
diff --git a/tests/environments/create-environment/collection-env-create.spec.ts b/tests/environments/create-environment/collection-env-create.spec.ts
index 01b0bd7cb..3c94b4d25 100644
--- a/tests/environments/create-environment/collection-env-create.spec.ts
+++ b/tests/environments/create-environment/collection-env-create.spec.ts
@@ -5,7 +5,6 @@ import {
createEnvironment,
addEnvironmentVariables,
saveEnvironment,
- closeEnvironmentPanel,
sendRequest,
expectResponseContains,
removeCollection
@@ -39,7 +38,6 @@ test.describe('Collection Environment Create Tests', () => {
]);
await saveEnvironment(page);
- await closeEnvironmentPanel(page);
await expect(locators.environment.currentEnvironment()).toContainText('Test Environment');
});
diff --git a/tests/environments/create-environment/global-env-create.spec.ts b/tests/environments/create-environment/global-env-create.spec.ts
index 49051f020..046ff9240 100644
--- a/tests/environments/create-environment/global-env-create.spec.ts
+++ b/tests/environments/create-environment/global-env-create.spec.ts
@@ -5,7 +5,6 @@ import {
createEnvironment,
addEnvironmentVariables,
saveEnvironment,
- closeEnvironmentPanel,
sendRequest,
expectResponseContains,
closeAllCollections
@@ -41,7 +40,6 @@ test.describe('Global Environment Create Tests', () => {
]);
await saveEnvironment(page);
- await closeEnvironmentPanel(page);
await expect(locators.environment.currentEnvironment()).toContainText('Test Global Environment');
});
diff --git a/tests/environments/export-environment/collection-env-export/collection-env-export.spec.ts b/tests/environments/export-environment/collection-env-export/collection-env-export.spec.ts
index 712cda34f..169a75e51 100644
--- a/tests/environments/export-environment/collection-env-export/collection-env-export.spec.ts
+++ b/tests/environments/export-environment/collection-env-export/collection-env-export.spec.ts
@@ -36,14 +36,13 @@ test.describe.serial('Collection Environment Export Tests', () => {
await page.getByTestId('env-tab-collection').click();
await page.getByText('Configure', { exact: true }).click();
- // Verify the environment settings modal opens
- const envModal = page.locator('.bruno-modal').filter({ hasText: 'Environments' });
- await expect(envModal).toBeVisible();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Open export modal and configure export settings', async () => {
// Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Verify export modal opens
const exportModal = page.locator('.bruno-modal').filter({ hasText: 'Export Environments' });
@@ -63,8 +62,6 @@ test.describe.serial('Collection Environment Export Tests', () => {
await test.step('Execute export and close modal', async () => {
// Export the environment
await page.getByRole('button', { name: 'Export 1 Environment' }).click();
-
- await page.getByTestId('modal-close-button').click();
});
await test.step('Verify exported file and content', async () => {
@@ -95,11 +92,14 @@ test.describe.serial('Collection Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-collection').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Configure export settings for multiple environments', async () => {
// Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Verify all environments are selected by default
await expect(page.getByRole('checkbox', { name: 'Local' })).toBeChecked();
@@ -115,8 +115,6 @@ test.describe.serial('Collection Environment Export Tests', () => {
await test.step('Execute export and close modal', async () => {
// Export all environments
await page.getByRole('button', { name: /Export \d+ Environments?/ }).click();
-
- await page.getByTestId('modal-close-button').click();
});
await test.step('Verify exported files and content', async () => {
@@ -162,11 +160,14 @@ test.describe.serial('Collection Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-collection').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Configure export settings with folder format', async () => {
// Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Select folder export format (default might be single JSON file)
await page.getByText('Separate files in folder').click();
@@ -178,8 +179,6 @@ test.describe.serial('Collection Environment Export Tests', () => {
await test.step('Execute export and close modal', async () => {
// Export should succeed with unique names
await page.getByRole('button', { name: 'Export 2 Environment' }).click();
-
- await page.getByTestId('modal-close-button').click();
});
await test.step('Verify unique naming and file content', async () => {
@@ -219,11 +218,13 @@ test.describe.serial('Collection Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-collection').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Configure export settings for single JSON file', async () => {
- // Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Deselect all environments first
await page.getByText('Deselect All').click();
@@ -244,8 +245,6 @@ test.describe.serial('Collection Environment Export Tests', () => {
// Verify success message
await expect(page.getByText('Environment(s) exported successfully', { exact: false }).first()).toBeVisible();
-
- await page.getByTestId('modal-close-button').click();
});
await test.step('Verify exported file and content', async () => {
@@ -274,11 +273,13 @@ test.describe.serial('Collection Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-collection').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Configure export settings for single JSON file', async () => {
- // Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Select single JSON file format
await page.getByText('Single JSON file').click();
@@ -293,8 +294,6 @@ test.describe.serial('Collection Environment Export Tests', () => {
// Verify success message
await expect(page.getByText('Environment(s) exported successfully', { exact: false }).first()).toBeVisible();
-
- await page.getByTestId('modal-close-button').click();
});
await test.step('Verify exported file and content', async () => {
@@ -329,11 +328,13 @@ test.describe.serial('Collection Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-collection').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Configure export settings for single JSON file', async () => {
- // Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Deselect all environments first
await page.getByText('Deselect All').click();
@@ -351,8 +352,6 @@ test.describe.serial('Collection Environment Export Tests', () => {
await test.step('Execute export and close modal', async () => {
// Export should succeed with unique names
await page.getByRole('button', { name: 'Export 1 Environment' }).click();
-
- await page.getByTestId('modal-close-button').click();
});
await test.step('Verify unique naming and file content', async () => {
@@ -387,11 +386,14 @@ test.describe.serial('Collection Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-collection').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Open export modal and deselect all environments', async () => {
// Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.getByRole('button', { name: 'Export Environment' }).click();
// Deselect all environments
await page.getByText('Deselect All').click();
diff --git a/tests/environments/export-environment/global-env-export/global-env-export.spec.ts b/tests/environments/export-environment/global-env-export/global-env-export.spec.ts
index 50f853936..bf0d39508 100644
--- a/tests/environments/export-environment/global-env-export/global-env-export.spec.ts
+++ b/tests/environments/export-environment/global-env-export/global-env-export.spec.ts
@@ -40,14 +40,13 @@ test.describe.serial('Global Environment Export Tests', () => {
await page.getByTestId('env-tab-global').click();
await page.getByText('Configure', { exact: true }).click();
- // Verify the global environment settings modal opens
- const globalEnvModal = page.locator('.bruno-modal').filter({ hasText: 'Global Environments' });
- await expect(globalEnvModal).toBeVisible();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Open export modal and configure export settings', async () => {
// Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Verify export modal opens
const exportModal = page.locator('.bruno-modal').filter({ hasText: 'Export Environments' });
@@ -67,8 +66,6 @@ test.describe.serial('Global Environment Export Tests', () => {
await test.step('Execute export and close modal', async () => {
// Export the environment
await page.getByRole('button', { name: 'Export 1 Environment' }).click();
-
- await page.getByTestId('modal-close-button').click();
});
await test.step('Verify exported file and content', async () => {
@@ -99,11 +96,14 @@ test.describe.serial('Global Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-global').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Configure export settings for multiple environments', async () => {
// Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Verify all environments are selected by default
await expect(page.getByRole('checkbox', { name: 'Local' })).toBeChecked();
@@ -119,8 +119,6 @@ test.describe.serial('Global Environment Export Tests', () => {
await test.step('Execute export and close modal', async () => {
// Export all environments
await page.getByRole('button', { name: /Export \d+ Environments?/ }).click();
-
- await page.getByTestId('modal-close-button').click();
});
await test.step('Verify exported files and content', async () => {
@@ -166,11 +164,14 @@ test.describe.serial('Global Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-global').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Configure export settings with folder format', async () => {
// Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Set export directory
await page.locator('input[id="export-location"]').fill(exportDir);
@@ -182,8 +183,6 @@ test.describe.serial('Global Environment Export Tests', () => {
await test.step('Execute export and close modal', async () => {
// Export should succeed with unique names
await page.getByRole('button', { name: 'Export 2 Environment' }).click();
-
- await page.getByTestId('modal-close-button').first().click();
});
await test.step('Verify unique naming and file content', async () => {
@@ -223,11 +222,13 @@ test.describe.serial('Global Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-global').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Configure export settings for single JSON file', async () => {
- // Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Deselect all environments first
await page.getByText('Deselect All').click();
@@ -250,8 +251,6 @@ test.describe.serial('Global Environment Export Tests', () => {
// Verify success message
await expect(page.getByText('Environment(s) exported successfully', { exact: false }).first()).toBeVisible();
-
- await page.getByTestId('modal-close-button').click();
});
await test.step('Verify exported file and content', async () => {
@@ -280,11 +279,13 @@ test.describe.serial('Global Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-global').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Configure export settings for single JSON file', async () => {
- // Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Select single JSON file format
await page.getByText('Single JSON file').click();
@@ -299,8 +300,6 @@ test.describe.serial('Global Environment Export Tests', () => {
await page.waitForTimeout(200);
// Verify success message
await expect(page.getByText('Environment(s) exported successfully', { exact: false }).first()).toBeVisible();
-
- await page.getByTestId('modal-close-button').click();
});
await test.step('Verify exported file and content', async () => {
@@ -335,11 +334,13 @@ test.describe.serial('Global Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-global').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Configure export settings for single JSON file', async () => {
- // Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.locator('button[title="Export environment"]').click();
// Deselect all environments first
await page.getByText('Deselect All').click();
@@ -359,8 +360,6 @@ test.describe.serial('Global Environment Export Tests', () => {
await test.step('Execute export and close modal', async () => {
// Export should succeed with unique names
await page.getByRole('button', { name: 'Export 1 Environment' }).click();
-
- await page.getByTestId('modal-close-button').first().click();
});
await test.step('Verify unique naming and file content', async () => {
@@ -395,18 +394,18 @@ test.describe.serial('Global Environment Export Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-global').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Open export modal and deselect all environments', async () => {
- // Click export button
- await page.locator('.btn-import-environment').getByText('Export').click();
+ await page.getByRole('button', { name: 'Export Environment' }).click();
- // Deselect all environments
await page.getByText('Deselect All').click();
});
await test.step('Verify export button is disabled when no environments selected', async () => {
- // Verify export button is disabled
await expect(page.getByRole('button', { name: 'Export Environments' })).toBeDisabled();
});
});
diff --git a/tests/environments/global-env-config-selection/global-env-config-selection.spec.ts b/tests/environments/global-env-config-selection/global-env-config-selection.spec.ts
index ae7513f96..d00245c63 100644
--- a/tests/environments/global-env-config-selection/global-env-config-selection.spec.ts
+++ b/tests/environments/global-env-config-selection/global-env-config-selection.spec.ts
@@ -21,15 +21,13 @@ test.describe('Global Environment Configuration Selection Tests', () => {
await page.getByTestId('env-tab-global').click();
await page.getByText('Configure', { exact: true }).click();
- // Verify the config modal opens with the currently active environment selected
- const globalEnvModal = page.locator('.bruno-modal').filter({ hasText: 'Global Environments' });
- await expect(globalEnvModal).toBeVisible();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
- // Check that the active environment in the config matches the current environment
- const activeEnvItem = globalEnvModal.locator('.environment-item.active');
+ const activeEnvItem = page.locator('.environment-item.active');
await expect(activeEnvItem).toContainText(currentEnvName);
- // Close the global environment config modal and go to welcome page
- await page.getByText('×').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
});
diff --git a/tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts b/tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts
index a7e544646..1dd030273 100644
--- a/tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts
+++ b/tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts
@@ -39,16 +39,14 @@ test.describe.serial('Collection Environment Import Tests', () => {
});
await test.step('Verify imported environment and variables', async () => {
- // The environment settings modal should now be visible with the imported environment
- const envModal = page.locator('.bruno-modal').filter({ hasText: 'Environments' });
- await expect(envModal).toBeVisible();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
- // Verify imported variables
await expect(page.getByRole('row', { name: 'host' }).getByRole('cell').nth(1)).toBeVisible();
await expect(page.getByRole('row', { name: 'secretToken' }).getByRole('cell').nth(1)).toBeVisible();
- // Close modal
- await page.getByText('×').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
await test.step('Clean up after test', async () => {
@@ -93,15 +91,11 @@ test.describe.serial('Collection Environment Import Tests', () => {
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(multiEnvFile);
- // The environment settings modal should now be visible with the imported environments
- const envModal = page.locator('.bruno-modal').filter({ hasText: 'Environments' });
- await expect(envModal).toBeVisible();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Verify both environments are available in selector', async () => {
- // Check that both environments are available in the selector
- await page.getByText('×').click(); // Close environment settings modal
-
await page.waitForTimeout(500);
await page.getByTestId('environment-selector-trigger').click();
@@ -118,15 +112,16 @@ test.describe.serial('Collection Environment Import Tests', () => {
// Verify prod environment variables by opening settings again
await page.getByTestId('environment-selector-trigger').click();
await page.getByText('Configure', { exact: true }).click();
- const envModal = page.locator('.bruno-modal').filter({ hasText: 'Environments' });
- await expect(envModal).toBeVisible();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
// Verify prod environment variables
await expect(page.getByRole('row', { name: 'host' }).getByRole('cell').nth(1)).toBeVisible();
await expect(page.getByRole('row', { name: 'secretToken' }).getByRole('cell').nth(1)).toBeVisible();
- // Close modal
- await page.getByText('×').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
await test.step('Clean up after test', async () => {
diff --git a/tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts b/tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts
index 39aebc79e..480af8412 100644
--- a/tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts
+++ b/tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts
@@ -23,12 +23,10 @@ test.describe.serial('Global Environment Import Tests', () => {
// Delete all existing environments
for (let i = 0; i < count; i++) {
- await page.getByTestId('delete-environment-button').click();
- // Confirm deletion if there's a confirmation dialog
+ await page.locator('button[title="Delete"]').first().click();
const confirmButton = page.getByRole('button', { name: 'Delete' });
if (await confirmButton.isVisible()) {
await confirmButton.click();
- await page.getByText('×').click();
}
}
@@ -56,16 +54,15 @@ test.describe.serial('Global Environment Import Tests', () => {
});
await test.step('Verify imported global environment and variables', async () => {
- // The global environment settings modal should now be visible with the imported environment
- const envModal = page.locator('.bruno-modal').filter({ hasText: 'Global Environments' });
- await expect(envModal).toBeVisible();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
// Verify imported variables
await expect(page.getByRole('row', { name: 'host' }).getByRole('cell').nth(1)).toBeVisible();
await expect(page.getByRole('row', { name: 'secretToken' }).getByRole('cell').nth(1)).toBeVisible();
- // Close modal
- await page.getByText('×').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
});
@@ -90,12 +87,10 @@ test.describe.serial('Global Environment Import Tests', () => {
// Delete all existing environments
for (let i = 0; i < count; i++) {
- await page.getByTestId('delete-environment-button').click();
- // Confirm deletion if there's a confirmation dialog
- const confirmButton = page.getByRole('button', { name: 'Delete' });
+ await page.locator('button[title="Delete"]').first().click();
+ const confirmButton = page.getByText('Delete', { exact: true });
if (await confirmButton.isVisible()) {
await confirmButton.click();
- await page.getByText('×').click();
}
}
@@ -120,14 +115,11 @@ test.describe.serial('Global Environment Import Tests', () => {
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(multiEnvFile);
- // The global environment settings modal should now be visible with the imported environments
- const envModal = page.locator('.bruno-modal').filter({ hasText: 'Global Environments' });
- await expect(envModal).toBeVisible();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
});
await test.step('Verify both global environments are available in selector', async () => {
- // Check that both environments are available in the selector
- await page.getByText('×').click(); // Close environment settings modal
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-global').click();
@@ -145,15 +137,15 @@ test.describe.serial('Global Environment Import Tests', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-global').click();
await page.getByText('Configure', { exact: true }).click();
- const envModal = page.locator('.bruno-modal').filter({ hasText: 'Global Environments' });
- await expect(envModal).toBeVisible();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
// Verify imported variables
await expect(page.getByRole('row', { name: 'host' }).getByRole('cell').nth(1)).toBeVisible();
await expect(page.getByRole('row', { name: 'secretToken' }).getByRole('cell').nth(1)).toBeVisible();
- // Close modal
- await page.getByText('×').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
});
});
diff --git a/tests/environments/import-environment/collection-env-import.spec.ts b/tests/environments/import-environment/collection-env-import.spec.ts
index 84ac2bd63..4e982c90a 100644
--- a/tests/environments/import-environment/collection-env-import.spec.ts
+++ b/tests/environments/import-environment/collection-env-import.spec.ts
@@ -52,21 +52,19 @@ test.describe('Collection Environment Import Tests', () => {
// 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();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).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();
+ await expect(page.locator('input[name="0.name"]')).toHaveValue('host');
+ await expect(page.locator('input[name="1.name"]')).toHaveValue('userId');
+ await expect(page.locator('input[name="2.name"]')).toHaveValue('apiKey');
+ await expect(page.locator('input[name="3.name"]')).toHaveValue('postTitle');
+ await expect(page.locator('input[name="4.name"]')).toHaveValue('postBody');
+ await expect(page.locator('input[name="5.name"]')).toHaveValue('secretApiToken');
+ await expect(page.locator('input[name="5.secret"]')).toBeChecked();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').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();
diff --git a/tests/environments/import-environment/global-env-import.spec.ts b/tests/environments/import-environment/global-env-import.spec.ts
index 9b05d1bd6..ab751e5f3 100644
--- a/tests/environments/import-environment/global-env-import.spec.ts
+++ b/tests/environments/import-environment/global-env-import.spec.ts
@@ -47,21 +47,20 @@ test.describe('Global Environment Import Tests', () => {
// 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();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).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();
+ const variablesTable = page.locator('.table-container');
+ await expect(variablesTable.locator('input[name="0.name"]')).toHaveValue('host');
+ await expect(variablesTable.locator('input[name="1.name"]')).toHaveValue('userId');
+ await expect(variablesTable.locator('input[name="2.name"]')).toHaveValue('apiKey');
+ await expect(variablesTable.locator('input[name="3.name"]')).toHaveValue('postTitle');
+ await expect(variablesTable.locator('input[name="4.name"]')).toHaveValue('postBody');
+ await expect(variablesTable.locator('input[name="5.name"]')).toHaveValue('secretApiToken');
+ await expect(variablesTable.locator('input[name="5.secret"]')).toBeChecked();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
- // Test GET request with global environment
await page.locator('#collection-environment-test-collection .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();
diff --git a/tests/environments/multiline-variables/write-multiline-variable.spec.ts b/tests/environments/multiline-variables/write-multiline-variable.spec.ts
index d774d88a3..ca4855be1 100644
--- a/tests/environments/multiline-variables/write-multiline-variable.spec.ts
+++ b/tests/environments/multiline-variables/write-multiline-variable.spec.ts
@@ -11,7 +11,7 @@ test.describe('Multiline Variables - Write Test', () => {
// open request
await expect(page.getByTitle('multiline-test', { exact: true })).toBeVisible();
- await page.getByTitle('multiline-test', { exact: true }).click();
+ await page.getByTitle('multiline-test', { exact: true }).dblclick();
// open environment dropdown
await page.locator('div.current-environment').click();
@@ -28,10 +28,12 @@ test.describe('Multiline Variables - Write Test', () => {
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();
- const valueTextarea = page.locator('.bruno-modal-card textarea').last();
- await expect(valueTextarea).toBeVisible();
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await expect(envTab).toBeVisible();
+
+ const emptyRowNameInput = page.locator('tbody tr').last().locator('input[placeholder="Name"]');
+ await expect(emptyRowNameInput).toBeVisible();
+ await emptyRowNameInput.fill('multiline_data_json');
const jsonValue = `{
"user": {
@@ -48,23 +50,17 @@ test.describe('Multiline Variables - Write Test', () => {
}
}`;
- // fill variable value
- await valueTextarea.fill(jsonValue);
- await page.keyboard.press('Shift+Tab');
- await page.keyboard.type('multiline_data_json');
+ const variableRow = page.locator('tbody tr').filter({ has: page.locator('input[value="multiline_data_json"]') });
+ const codeMirror = variableRow.locator('.CodeMirror');
+ await codeMirror.click();
+ await page.keyboard.insertText(jsonValue);
- // save variable and close config
- const saveVarButton = page.getByRole('button', { name: /Save/i });
- await expect(saveVarButton).toBeVisible();
- await saveVarButton.click();
+ await page.getByTestId('save-env').click();
- await expect(page.locator('.close.cursor-pointer')).toBeVisible();
- await page.locator('.close.cursor-pointer').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
- // send request
- const sendButton = page.locator('#send-request').getByRole('img').nth(2);
- await expect(sendButton).toBeVisible();
- await sendButton.click();
+ await page.getByTestId('send-arrow-icon').click();
// wait for response status
await expect(page.locator('.response-status-code.text-ok')).toBeVisible();
diff --git a/tests/environments/update-global-environment-via-script/global-env-update-via-script.spec.ts b/tests/environments/update-global-environment-via-script/global-env-update-via-script.spec.ts
index 38d340a88..e57834ec8 100644
--- a/tests/environments/update-global-environment-via-script/global-env-update-via-script.spec.ts
+++ b/tests/environments/update-global-environment-via-script/global-env-update-via-script.spec.ts
@@ -3,7 +3,6 @@ import { closeAllCollections } from '../../utils/page';
test.describe('Global Environment Variable Update via Script', () => {
test.afterEach(async ({ pageWithUserData: page }) => {
- // cleanup: close all collections
await closeAllCollections(page);
});
@@ -23,40 +22,43 @@ test.describe('Global Environment Variable Update via Script', () => {
await page.getByTestId('send-arrow-icon').click();
});
- await test.step('Open the Global Environment Config modal', async () => {
+ await test.step('Open the Global Environment Config tab', async () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-global').click();
await page.getByText('Configure', { exact: true }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
});
- const globalEnvModal = page.locator('.bruno-modal').filter({ hasText: 'Global Environments' });
-
await test.step('Verify that the value of "existingEnvEnabled" is updated by the pre-request script', async () => {
- const updatedExistingEnvEnabledInputDiv = await globalEnvModal.getByTestId('env-var-value-1');
- const updatedExistingEnvEnabledValue = await updatedExistingEnvEnabledInputDiv.locator('.CodeMirror-line').textContent();
- await expect(updatedExistingEnvEnabledValue).toContain('newExistingEnvEnabledValue');
+ const row = page.locator('tbody tr').filter({ has: page.locator('input[value="existingEnvEnabled"]') });
+ const value = await row.locator('.CodeMirror-line').first().textContent();
+ await expect(value).toContain('newExistingEnvEnabledValue');
});
await test.step('Verify that the value of "existingEnvDisabled" is updated by the pre-request script', async () => {
- const updatedExistingEnvDisabledInputDiv = await globalEnvModal.getByTestId('env-var-value-2');
- const updatedExistingEnvDisabledValue = await updatedExistingEnvDisabledInputDiv.locator('.CodeMirror-line').textContent();
- await expect(updatedExistingEnvDisabledValue).toContain('newExistingEnvDisabledValue');
+ const row = page.locator('tbody tr').filter({ has: page.locator('input[value="existingEnvDisabled"]') });
+ const value = await row.locator('.CodeMirror-line').first().textContent();
+ await expect(value).toContain('newExistingEnvDisabledValue');
});
await test.step('Verify that a new env variable "newEnv" is added by the pre-request script to the global environment', async () => {
- const newEnvInputDiv = await globalEnvModal.getByTestId('env-var-value-3');
- const newEnvValue = await newEnvInputDiv.locator('.CodeMirror-line').textContent();
- await expect(newEnvValue).toContain('newEnvValue');
+ const row = page.locator('tbody tr').filter({ has: page.locator('input[value="newEnv"]') });
+ const value = await row.locator('.CodeMirror-line').first().textContent();
+ await expect(value).toContain('newEnvValue');
});
await test.step('Verify that the value of "baseUrl" is unchanged.', async () => {
- const currentBaseUrlInputDiv = await globalEnvModal.getByTestId('env-var-value-0');
- const currentBaseUrlValue = await currentBaseUrlInputDiv.locator('.CodeMirror-line').textContent();
- await expect(currentBaseUrlValue).toContain('https://echo.usebruno.com');
+ const row = page.locator('tbody tr').filter({ has: page.locator('input[value="baseUrl"]') });
+ const value = await row.locator('.CodeMirror-line').first().textContent();
+ await expect(value).toContain('https://echo.usebruno.com');
});
- await test.step('Close the global environment config modal.', async () => {
- await page.getByTestId('modal-close-button').click();
+ await test.step('Close the global environment config tab.', async () => {
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
});
});
diff --git a/tests/global-environments/non-string-values.spec.ts b/tests/global-environments/non-string-values.spec.ts
index 4fe5e8774..395370ca2 100644
--- a/tests/global-environments/non-string-values.spec.ts
+++ b/tests/global-environments/non-string-values.spec.ts
@@ -1,5 +1,5 @@
import { test, expect } from '../../playwright';
-import { openCollectionAndAcceptSandbox, closeAllCollections, sendRequest } from '../utils/page';
+import { openCollectionAndAcceptSandbox, closeAllCollections, sendRequest, addEnvironmentVariables } from '../utils/page';
import { buildCommonLocators } from '../utils/page/locators';
test.describe('Global Environment Variables - Non-string Values', () => {
@@ -23,20 +23,19 @@ test.describe('Global Environment Variables - Non-string Values', () => {
await page.locator('#environment-name').fill('Test Env');
await page.getByRole('button', { name: 'Create', exact: true }).click();
- // Add a string variable.
- await page.getByTestId('add-variable').click();
- const newRow = page.locator('tbody tr').last();
- await newRow.locator('input[name$=".name"]').fill('stringVar');
- await newRow.locator('.CodeMirror').click();
- await page.keyboard.type('hello world');
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
+
+ await addEnvironmentVariables(page, [
+ { name: 'stringVar', value: 'hello world' },
+ { name: 'numericVar', value: '170001' },
+ { name: 'booleanVar', value: 'true' }
+ ]);
- // Save
await page.getByTestId('save-env').click();
- // Verify that the string variable value is saved and displayed correctly.
- await expect(newRow.locator('.CodeMirror-line').first()).toContainText('hello world');
- // Close the environment modal
- await page.locator('[data-test-id="modal-close-button"]').click();
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
// Request contains a script that sets the non-string global variables.
@@ -50,14 +49,13 @@ test.describe('Global Environment Variables - Non-string Values', () => {
await page.getByTestId('environment-selector-trigger').click();
await page.getByTestId('env-tab-global').click();
await page.getByRole('button', { name: 'Configure' }).click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await expect(envTab).toBeVisible();
});
- const envModal = page
- .locator('.bruno-modal-card')
- .filter({ has: page.locator('.bruno-modal-header-title', { hasText: 'Global Environments' }) });
-
- const numericInput = envModal.locator('input[value="numericVar"]');
- const booleanInput = envModal.locator('input[value="booleanVar"]');
+ const numericInput = page.locator('input[value="numericVar"]');
+ const booleanInput = page.locator('input[value="booleanVar"]');
await expect(numericInput).toBeVisible();
await expect(booleanInput).toBeVisible();
const numericRow = numericInput.locator('xpath=ancestor::tr');
@@ -74,9 +72,7 @@ test.describe('Global Environment Variables - Non-string Values', () => {
await page.keyboard.type('999');
await expect(numericRow.locator('.CodeMirror-line').first()).toContainText(/170001/);
- // Hovering over the info icon reveals the tooltip.
- // It is anchored to the info icon element id, so hover/click reveals it reliably.
- const infoIcon = page.locator('#numericVar-disabled-info-icon');
+ const infoIcon = numericRow.locator('[id$="-disabled-info-icon"]').nth(0);
await infoIcon.hover();
// The tooltip explains why the field is locked.
@@ -103,8 +99,7 @@ test.describe('Global Environment Variables - Non-string Values', () => {
await page.keyboard.type('false');
await expect(booleanRow.locator('.CodeMirror-line').first()).toContainText(/true/);
- // Hovering over the info icon reveals the tooltip.
- const infoIcon = page.locator('#booleanVar-disabled-info-icon');
+ const infoIcon = booleanRow.locator('[id$="-disabled-info-icon"]').nth(0);
await infoIcon.hover();
// The tooltip explains why the field is locked.
@@ -114,8 +109,7 @@ test.describe('Global Environment Variables - Non-string Values', () => {
});
await test.step('Verify that stringVar remains editable', async () => {
- // Unlike script-managed values above, this one is user-managed.
- const stringInput = envModal.locator('input[value="stringVar"]');
+ const stringInput = page.locator('input[value="stringVar"]');
await expect(stringInput).toBeVisible();
const stringRow = stringInput.locator('xpath=ancestor::tr');
@@ -125,8 +119,12 @@ test.describe('Global Environment Variables - Non-string Values', () => {
// Verify the user edit persists in the UI.
await expect(stringRow.locator('.CodeMirror-line').first()).toContainText('hello world updated');
- // Close the environment modal
- await page.locator('[data-test-id="modal-close-button"]').click();
+
+ await page.getByTestId('save-env').click();
+
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
});
});
diff --git a/tests/import/insomnia/import-insomnia-v4-environments.spec.ts b/tests/import/insomnia/import-insomnia-v4-environments.spec.ts
index 4eaabdd69..9b1b9bdef 100644
--- a/tests/import/insomnia/import-insomnia-v4-environments.spec.ts
+++ b/tests/import/insomnia/import-insomnia-v4-environments.spec.ts
@@ -178,9 +178,10 @@ test.describe('Import Insomnia v4 Collection - Environment Import', () => {
await expect(page.getByTestId('env-var-row-newFeature.version').locator('.CodeMirror-line').first()).toHaveText('2.099123123');
});
- await test.step('Close environment modal', async () => {
- // Close the environment configuration modal to ensure clean state
- await page.getByText('×').click();
+ await test.step('Close environment tab', async () => {
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
});
});
diff --git a/tests/import/insomnia/import-insomnia-v5-environments.spec.ts b/tests/import/insomnia/import-insomnia-v5-environments.spec.ts
index 25555e2a8..11fca239e 100644
--- a/tests/import/insomnia/import-insomnia-v5-environments.spec.ts
+++ b/tests/import/insomnia/import-insomnia-v5-environments.spec.ts
@@ -202,9 +202,10 @@ test.describe('Import Insomnia v5 Collection - Environment Import', () => {
await expect(page.getByTestId('env-var-row-user.roles[0]').locator('.CodeMirror-line').first()).toHaveText('admin');
});
- await test.step('Close environment modal', async () => {
- // Close the environment configuration modal to ensure clean state
- await page.getByText('×').click();
+ await test.step('Close environment tab', async () => {
+ const envTab = page.locator('.request-tab').filter({ hasText: 'Environments' });
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
});
});
diff --git a/tests/request/newlines/newlines-persistence.spec.ts b/tests/request/newlines/newlines-persistence.spec.ts
index 775871840..2d019ee01 100644
--- a/tests/request/newlines/newlines-persistence.spec.ts
+++ b/tests/request/newlines/newlines-persistence.spec.ts
@@ -16,6 +16,8 @@ test('should persist request with newlines across app restarts', async ({ create
await page.locator('.bruno-modal').getByLabel('Location').fill(collectionPath);
await page.locator('.bruno-modal').getByRole('button', { name: 'Create' }).click();
+ await openCollectionAndAcceptSandbox(page, 'newlines-persistence', 'safe');
+
const collection = page.getByTestId('collections').locator('.collection-name').filter({ hasText: 'newlines-persistence' });
await collection.hover();
await collection.locator('.collection-actions .icon').click();
@@ -25,7 +27,6 @@ test('should persist request with newlines across app restarts', async ({ create
await page.locator('#new-request-url').locator('textarea').fill('https://httpbin.org/get');
await page.locator('.bruno-modal').getByRole('button', { name: 'Create', exact: true }).click();
- await openCollectionAndAcceptSandbox(page, 'newlines-persistence', 'safe');
await page.locator('.collection-item-name').filter({ hasText: 'persistence-test' }).dblclick();
await page.getByRole('tab', { name: 'Params' }).click();
diff --git a/tests/utils/page/actions.ts b/tests/utils/page/actions.ts
index a4da13a8c..611dd2c29 100644
--- a/tests/utils/page/actions.ts
+++ b/tests/utils/page/actions.ts
@@ -390,10 +390,23 @@ const createEnvironment = async (
await page.locator('button[id="create-env"]').click();
- const nameInput = page.locator('input[name="name"]');
+ const nameInput = type === 'collection'
+ ? page.locator('input[name="name"]')
+ : page.locator('#environment-name');
await expect(nameInput).toBeVisible();
await nameInput.fill(environmentName);
await page.getByRole('button', { name: 'Create' }).click();
+
+ const tabLabel = type === 'collection' ? 'Environments' : 'Global Environments';
+ await expect(page.locator('.request-tab').filter({ hasText: tabLabel })).toBeVisible();
+
+ const locators = buildCommonLocators(page);
+ await locators.environment.selector().click();
+ if (type === 'global') {
+ await locators.environment.globalTab().click();
+ }
+ await locators.environment.envOption(environmentName).click();
+ await expect(page.locator('.current-environment')).toContainText(environmentName);
});
};
@@ -416,11 +429,6 @@ const addEnvironmentVariable = async (
index: number
) => {
await test.step(`Add environment variable "${variable.name}"`, async () => {
- const addButton = page.locator('button[data-testid="add-variable"]');
- await addButton.waitFor({ state: 'visible' });
- await addButton.click();
-
- // Wait for the new row to be added and the name input to be visible
const nameInput = page.locator(`input[name="${index}.name"]`);
await nameInput.waitFor({ state: 'visible' });
await nameInput.fill(variable.name);
@@ -466,13 +474,17 @@ const saveEnvironment = async (page: Page) => {
};
/**
- * Close the environment modal/panel
+ * Close the environment tab
* @param page - The page object
+ * @param type - The type of environment tab to close
* @returns void
*/
-const closeEnvironmentPanel = async (page: Page) => {
- await test.step('Close environment panel', async () => {
- await page.getByText('×').click();
+const closeEnvironmentPanel = async (page: Page, type: EnvironmentType = 'collection') => {
+ await test.step('Close environment tab', async () => {
+ const tabLabel = type === 'collection' ? 'Environments' : 'Global Environments';
+ const envTab = page.locator('.request-tab').filter({ hasText: tabLabel });
+ await envTab.hover();
+ await envTab.getByTestId('request-tab-close-icon').click();
});
};
diff --git a/tests/utils/page/locators.ts b/tests/utils/page/locators.ts
index 67efa05ee..f822b1c89 100644
--- a/tests/utils/page/locators.ts
+++ b/tests/utils/page/locators.ts
@@ -39,7 +39,7 @@ export const buildCommonLocators = (page: Page) => ({
tabs: {
requestTab: (requestName: string) => page.locator('.request-tab .tab-label').filter({ hasText: requestName }),
activeRequestTab: () => page.locator('.request-tab.active'),
- closeTab: (requestName: string) => page.locator('.request-tab').filter({ hasText: requestName }).locator('.close-icon')
+ closeTab: (requestName: string) => page.locator('.request-tab').filter({ hasText: requestName }).getByTestId('request-tab-close-icon')
},
folder: {
chevron: (folderName: string) => page.locator('.collection-item-name').filter({ hasText: folderName }).getByTestId('folder-chevron')