mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat(sandbox): create a dropdown selector for sandbox mode (#6519)
This commit is contained in:
@@ -19,7 +19,6 @@ import CollectionSettings from 'components/CollectionSettings';
|
||||
import { DocExplorer } from '@usebruno/graphql-docs';
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import SecuritySettings from 'components/SecuritySettings';
|
||||
import FolderSettings from 'components/FolderSettings';
|
||||
import { getGlobalEnvironmentVariables, getGlobalEnvironmentVariablesMasked } from 'utils/collections/index';
|
||||
import { produce } from 'immer';
|
||||
@@ -226,10 +225,6 @@ const RequestTabPanel = () => {
|
||||
return <FolderSettings collection={collection} folder={folder} />;
|
||||
}
|
||||
|
||||
if (focusedTab.type === 'security-settings') {
|
||||
return <SecuritySettings collection={collection} />;
|
||||
}
|
||||
|
||||
if (focusedTab.type === 'environment-settings') {
|
||||
return <EnvironmentSettings collection={collection} />;
|
||||
}
|
||||
|
||||
@@ -65,9 +65,8 @@ const CollectionToolBar = ({ collection }) => {
|
||||
<IconSettings size={16} strokeWidth={1.5} />
|
||||
</ActionIcon>
|
||||
</ToolHint>
|
||||
<ToolHint text="Javascript Sandbox" toolhintId="JavascriptSandboxToolhintId" place="bottom">
|
||||
<JsSandboxMode collection={collection} />
|
||||
</ToolHint>
|
||||
{/* ToolHint is present within the JsSandboxMode component */}
|
||||
<JsSandboxMode collection={collection} />
|
||||
<span className="ml-2">
|
||||
<EnvironmentSelector collection={collection} />
|
||||
</span>
|
||||
|
||||
@@ -21,14 +21,6 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'security-settings': {
|
||||
return (
|
||||
<>
|
||||
<IconShieldLock size={14} strokeWidth={1.5} className="text-yellow-600 flex-shrink-0" />
|
||||
<span className="ml-1 tab-name">Security</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'folder-settings': {
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -172,7 +172,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
|
||||
setShowConfirmGlobalEnvironmentClose(true);
|
||||
};
|
||||
|
||||
if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'security-settings', 'environment-settings', 'global-environment-settings'].includes(tab.type)) {
|
||||
if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'environment-settings', 'global-environment-settings'].includes(tab.type)) {
|
||||
return (
|
||||
<StyledWrapper
|
||||
className={`flex items-center justify-between tab-container px-2 ${tab.preview ? 'italic' : ''}`}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import styled from 'styled-components';
|
||||
import { rgba } from 'polished';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.sandbox-icon {
|
||||
@@ -12,19 +13,156 @@ const StyledWrapper = styled.div`
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
background-color: ${(props) => props.theme.background.surface0};
|
||||
}
|
||||
}
|
||||
|
||||
.safe-mode {
|
||||
background-color: ${(props) => props.theme.app.collection.toolbar.sandboxMode.safeMode.bg};
|
||||
color: ${(props) => props.theme.app.collection.toolbar.sandboxMode.safeMode.color};
|
||||
}
|
||||
|
||||
.developer-mode {
|
||||
background-color: ${(props) => props.theme.app.collection.toolbar.sandboxMode.developerMode.bg};
|
||||
color: ${(props) => props.theme.app.collection.toolbar.sandboxMode.developerMode.color};
|
||||
}
|
||||
|
||||
.sandbox-dropdown {
|
||||
min-width: 260px;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
.sandbox-header {
|
||||
padding: 0.5rem 0.625rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
color: ${(props) => props.theme.dropdown.headingText};
|
||||
}
|
||||
|
||||
.sandbox-option {
|
||||
display: flex;
|
||||
margin: 5px;
|
||||
border-radius: ${(props) => props.theme.border.radius.md};
|
||||
padding: 12px;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
gap: 0.5rem;
|
||||
position: relative;
|
||||
|
||||
&.safe-mode {
|
||||
border: 1px solid ${(props) => props.theme.input.border};
|
||||
color: ${(props) => props.theme.colors.text.green};
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&.developer-mode {
|
||||
border: 1px solid ${(props) => props.theme.input.border};
|
||||
color: ${(props) => props.theme.colors.text.warning};
|
||||
}
|
||||
|
||||
&.active {
|
||||
cursor: default;
|
||||
|
||||
&.developer-mode {
|
||||
border: 1px solid ${(props) => props.theme.colors.text.warning};
|
||||
background-color: ${(props) => rgba(props.theme.colors.text.warning, 0.04)};
|
||||
|
||||
.sandbox-option-radio input:checked {
|
||||
border-color: ${(props) => props.theme.colors.text.warning};
|
||||
}
|
||||
|
||||
.sandbox-option-radio input::after {
|
||||
background: ${(props) => props.theme.colors.text.warning};
|
||||
}
|
||||
}
|
||||
|
||||
&.safe-mode {
|
||||
border: 1px solid ${(props) => props.theme.colors.text.green};
|
||||
background-color: ${(props) => rgba(props.theme.colors.text.green, 0.04)};
|
||||
|
||||
.sandbox-option-radio input:checked {
|
||||
border-color: ${(props) => props.theme.colors.text.green};
|
||||
}
|
||||
|
||||
.sandbox-option-radio input::after {
|
||||
background: ${(props) => props.theme.colors.text.green};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.recommended-badge {
|
||||
padding: 0.125rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
background-color: ${(props) => rgba(props.theme.colors.text.green, 0.1)};
|
||||
color: ${(props) => props.theme.colors.text.green};
|
||||
border-radius: ${(props) => props.theme.border.radius.sm};
|
||||
}
|
||||
|
||||
.sandbox-option-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
gap: 0.25rem;
|
||||
line-height: 1.25rem;
|
||||
color: ${(props) => props.theme.colors.text.subtext2};
|
||||
}
|
||||
|
||||
.sandbox-option-radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.sandbox-option-radio input {
|
||||
appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 9999px;
|
||||
border: 1px solid currentColor;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.sandbox-option-radio input::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 3px;
|
||||
border-radius: 9999px;
|
||||
background: ${(props) => props.theme.background.base};
|
||||
opacity: 0;
|
||||
transform: scale(0.5);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.sandbox-option-radio input:checked::after {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.sandbox-option-radio input:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sandbox-option-description {
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
line-height: 1.1rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.developer-mode-warning {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
display: inline-block;
|
||||
background-color: ${(props) => rgba(props.theme.colors.text.warning, 0.1)};
|
||||
border-radius: ${(props) => props.theme.border.radius.sm};
|
||||
color: ${(props) => props.theme.colors.text.warning};
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,45 +1,126 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import toast from 'react-hot-toast';
|
||||
import { IconShieldCheck, IconCode } from '@tabler/icons';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { uuid } from 'utils/common/index';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { saveCollectionSecurityConfig } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
|
||||
const SANDBOX_OPTIONS = [
|
||||
{
|
||||
key: 'safe',
|
||||
label: 'Safe Mode',
|
||||
description: 'JavaScript code is executed in a secure sandbox and cannot access your filesystem or execute system commands.',
|
||||
icon: IconShieldCheck,
|
||||
recommended: true
|
||||
},
|
||||
{
|
||||
key: 'developer',
|
||||
label: 'Developer Mode',
|
||||
description: 'JavaScript code has access to the filesystem, can execute system commands and access sensitive information.',
|
||||
icon: IconCode,
|
||||
warning: 'Use only if you trust the authors of the collection',
|
||||
recommended: false
|
||||
}
|
||||
];
|
||||
|
||||
const JsSandboxMode = ({ collection }) => {
|
||||
const jsSandboxMode = collection?.securityConfig?.jsSandboxMode || 'safe';
|
||||
const dispatch = useDispatch();
|
||||
const dropdownRef = useRef(null);
|
||||
const [selectedMode, setSelectedMode] = useState(collection?.securityConfig?.jsSandboxMode || 'safe');
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedMode(collection?.securityConfig?.jsSandboxMode || 'safe');
|
||||
}, [collection?.securityConfig?.jsSandboxMode]);
|
||||
|
||||
const onDropdownCreate = (instance) => {
|
||||
dropdownRef.current = instance;
|
||||
};
|
||||
|
||||
const closeDropdown = () => {
|
||||
dropdownRef.current?.hide();
|
||||
};
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e && e.key === 'Escape') {
|
||||
closeDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
const handleModeChange = (mode) => {
|
||||
if (!collection?.uid || mode === selectedMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewSecuritySettings = () => {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: uuid(),
|
||||
collectionUid: collection.uid,
|
||||
type: 'security-settings'
|
||||
saveCollectionSecurityConfig(collection.uid, {
|
||||
jsSandboxMode: mode
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
setSelectedMode(mode);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
toast.error('Failed to update sandbox mode');
|
||||
});
|
||||
};
|
||||
|
||||
const renderOption = (option) => {
|
||||
const OptionIcon = option.icon;
|
||||
const isActive = selectedMode === option.key;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={option.key}
|
||||
className={`sandbox-option ${option.key}-mode ${isActive ? 'active' : ''}`}
|
||||
onClick={() => handleModeChange(option.key)}
|
||||
role="menuitemradio"
|
||||
aria-checked={isActive}
|
||||
data-testid={`sandbox-mode-${option.key}`}
|
||||
>
|
||||
|
||||
<div className="dropdown-label">
|
||||
<div className="sandbox-option-title">
|
||||
<div className="sandbox-option-radio">
|
||||
<input
|
||||
type="radio"
|
||||
name="sandbox-mode"
|
||||
value={option.key}
|
||||
checked={isActive}
|
||||
/>
|
||||
</div>
|
||||
<OptionIcon size={24} strokeWidth={1.5} />
|
||||
{option.label}
|
||||
{option.recommended && <span className="recommended-badge">Recommended</span>}
|
||||
</div>
|
||||
{option.warning && (<div><span className="developer-mode-warning">{option.warning}</span></div>)}
|
||||
<div className="sandbox-option-description">{option.description}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const triggerIcon = (
|
||||
<div>
|
||||
<ToolHint text={`${selectedMode === 'developer' ? 'Developer Mode' : 'Safe Mode'}`} toolhintId="JavascriptSandboxToolhintId" place="bottom">
|
||||
<div className={`sandbox-icon ${selectedMode === 'developer' ? 'developer-mode' : 'safe-mode'}`} data-testid="sandbox-mode-selector">
|
||||
{selectedMode === 'developer' ? <IconCode size={14} strokeWidth={2} /> : <IconShieldCheck size={14} strokeWidth={2} />}
|
||||
</div>
|
||||
</ToolHint>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex">
|
||||
{jsSandboxMode === 'safe' && (
|
||||
<div
|
||||
className="sandbox-icon safe-mode"
|
||||
data-testid="sandbox-mode-selector"
|
||||
onClick={viewSecuritySettings}
|
||||
title="Safe Mode"
|
||||
>
|
||||
<IconShieldCheck size={14} strokeWidth={2} />
|
||||
<StyledWrapper className="flex" onKeyDown={handleKeyDown}>
|
||||
<Dropdown onCreate={onDropdownCreate} icon={triggerIcon} placement="bottom-start">
|
||||
<div className="sandbox-dropdown">
|
||||
<div className="sandbox-header">JavaScript Sandbox</div>
|
||||
{SANDBOX_OPTIONS.map(renderOption)}
|
||||
</div>
|
||||
)}
|
||||
{jsSandboxMode === 'developer' && (
|
||||
<div
|
||||
className="sandbox-icon developer-mode"
|
||||
data-testid="sandbox-mode-selector"
|
||||
onClick={viewSecuritySettings}
|
||||
title="Developer Mode"
|
||||
>
|
||||
<IconCode size={14} strokeWidth={2} />
|
||||
</div>
|
||||
)}
|
||||
</Dropdown>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
max-width: 800px;
|
||||
|
||||
span.developer-mode-warning {
|
||||
font-weight: 400;
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -1,83 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { saveCollectionSecurityConfig } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import toast from 'react-hot-toast';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Button from 'ui/Button';
|
||||
|
||||
const SecuritySettings = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [jsSandboxMode, setJsSandboxMode] = useState(collection?.securityConfig?.jsSandboxMode || 'safe');
|
||||
|
||||
const handleChange = (e) => {
|
||||
setJsSandboxMode(e.target.value);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
dispatch(
|
||||
saveCollectionSecurityConfig(collection?.uid, {
|
||||
jsSandboxMode: jsSandboxMode
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
toast.success('Sandbox mode updated successfully');
|
||||
})
|
||||
.catch((err) => console.log(err) && toast.error('Failed to update sandbox mode'));
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relative px-4 py-4">
|
||||
<div className="font-medium mt-2">JavaScript Sandbox</div>
|
||||
|
||||
<div className="mt-4">
|
||||
The collection might include JavaScript code in Variables, Scripts, Tests, and Assertions.
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col mt-4">
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="safe" className="flex flex-row items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="safe"
|
||||
name="jsSandboxMode"
|
||||
value="safe"
|
||||
checked={jsSandboxMode === 'safe'}
|
||||
onChange={handleChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
<span className={jsSandboxMode === 'safe' ? 'font-medium' : 'font-normal'}>
|
||||
Safe Mode
|
||||
</span>
|
||||
</label>
|
||||
<p className="text-muted mt-1">
|
||||
JavaScript code is executed in a secure sandbox and cannot access your filesystem or execute system commands.
|
||||
</p>
|
||||
|
||||
<label htmlFor="developer" className="flex flex-row gap-2 mt-6 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="developer"
|
||||
name="jsSandboxMode"
|
||||
value="developer"
|
||||
checked={jsSandboxMode === 'developer'}
|
||||
onChange={handleChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
<span className={jsSandboxMode === 'developer' ? 'font-medium' : 'font-normal'}>
|
||||
Developer Mode
|
||||
<span className="ml-1 developer-mode-warning">(use only if you trust the authors of the collection)</span>
|
||||
</span>
|
||||
</label>
|
||||
<p className="text-muted mt-1">
|
||||
JavaScript code has access to the filesystem, can execute system commands and access sensitive information.
|
||||
</p>
|
||||
</div>
|
||||
<Button size="sm" onClick={handleSave} className="w-fit mt-6">
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecuritySettings;
|
||||
@@ -24,7 +24,6 @@ export const tabsSlice = createSlice({
|
||||
const nonReplaceableTabTypes = [
|
||||
'variables',
|
||||
'collection-runner',
|
||||
'security-settings',
|
||||
'environment-settings',
|
||||
'global-environment-settings'
|
||||
];
|
||||
|
||||
@@ -1,39 +1,28 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import { createCollection, openCollection } from '../../utils/page/actions';
|
||||
import { buildSandboxLocators } from '../../utils/page/locators';
|
||||
|
||||
test.describe('Default JavaScript Sandbox Mode', () => {
|
||||
test('should set jsSandboxMode to safe by default when creating a new collection', async ({ page, createTmpDir }) => {
|
||||
const collectionName = 'test-sandbox-collection';
|
||||
|
||||
await createCollection(page, collectionName, await createTmpDir());
|
||||
const sandboxLocators = buildSandboxLocators(page);
|
||||
|
||||
// Verify sandbox mode is set to safe by default
|
||||
const sandboxModeSelector = page.getByTestId('sandbox-mode-selector');
|
||||
await expect(sandboxModeSelector).toBeVisible();
|
||||
await expect(sandboxModeSelector).toHaveAttribute('title', 'Safe Mode');
|
||||
await expect(sandboxLocators.sandboxModeSelector()).toBeVisible();
|
||||
|
||||
// Click on sandbox mode selector to open security settings
|
||||
await sandboxModeSelector.click();
|
||||
await sandboxLocators.sandboxModeSelector().click();
|
||||
|
||||
// Change to developer mode
|
||||
const developerRadio = page.locator('input[id="developer"]');
|
||||
await developerRadio.click();
|
||||
const developerRadio = sandboxLocators.developerModeRadio();
|
||||
await developerRadio.check();
|
||||
|
||||
// Save
|
||||
const saveButton = page.getByRole('button', { name: 'Save' });
|
||||
await saveButton.click();
|
||||
// For developer mode, check if safe mode is currently selected
|
||||
const safeModeChecked = await sandboxLocators.safeModeRadio().isChecked().catch(() => false);
|
||||
await expect(safeModeChecked).toBe(false);
|
||||
|
||||
// Verify mode changed to developer
|
||||
await expect(sandboxModeSelector).toHaveAttribute('title', 'Developer Mode');
|
||||
|
||||
// Close all tabs
|
||||
const modifier = process.platform === 'darwin' ? 'Meta' : 'Control';
|
||||
await page.keyboard.press(`${modifier}+Shift+W`);
|
||||
|
||||
// Reopen the collection
|
||||
await openCollection(page, collectionName);
|
||||
|
||||
// Verify mode is still developer (persisted)
|
||||
await expect(sandboxModeSelector).toHaveAttribute('title', 'Developer Mode');
|
||||
await page.keyboard.press('Escape');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,3 +201,16 @@ export const buildGrpcCommonLocators = (page: Page) => ({
|
||||
tabCount: () => page.getByTestId('tab-response-count')
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Builds locators for sandbox mode settings
|
||||
* @param page - The Playwright page object
|
||||
* @returns Object with locators for sandbox elements
|
||||
*/
|
||||
export const buildSandboxLocators = (page: Page) => ({
|
||||
sandboxModeSelector: () => page.getByTestId('sandbox-mode-selector'),
|
||||
safeModeRadio: () => page.getByTestId('sandbox-mode-safe'),
|
||||
developerModeRadio: () => page.getByTestId('sandbox-mode-developer'),
|
||||
jsSandboxHeading: () => page.getByText('JavaScript Sandbox'),
|
||||
saveButton: () => page.getByRole('button', { name: 'Save' })
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Page, expect, test } from '../../../playwright';
|
||||
import { buildSandboxLocators } from './locators';
|
||||
|
||||
/**
|
||||
* Builds locators for the runner results view
|
||||
@@ -78,19 +79,6 @@ export const runCollection = async (page: Page, collectionName: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds locators for sandbox mode settings
|
||||
* @param page - The Playwright page object
|
||||
* @returns Object with locators for sandbox elements
|
||||
*/
|
||||
export const buildSandboxLocators = (page: Page) => ({
|
||||
sandboxModeSelector: () => page.getByTestId('sandbox-mode-selector'),
|
||||
safeModeRadio: () => page.getByLabel('Safe Mode'),
|
||||
developerModeRadio: () => page.getByLabel('Developer Mode(use only if'),
|
||||
jsSandboxHeading: () => page.getByText('JavaScript Sandbox'),
|
||||
saveButton: () => page.getByRole('button', { name: 'Save' })
|
||||
});
|
||||
|
||||
/**
|
||||
* Sets up the JavaScript sandbox mode for a collection
|
||||
* @param page - The Playwright page object
|
||||
@@ -128,23 +116,12 @@ export const setSandboxMode = async (page: Page, collectionName: string, mode: '
|
||||
await sandboxLocators.developerModeRadio().waitFor({ state: 'visible', timeout: 5000 });
|
||||
await sandboxLocators.developerModeRadio().check();
|
||||
} else {
|
||||
// For safe mode, check if developer mode is currently selected
|
||||
const developerModeChecked = await sandboxLocators.developerModeRadio().isChecked().catch(() => false);
|
||||
|
||||
if (developerModeChecked) {
|
||||
// Click the Developer Mode label text inside the security settings form
|
||||
const securityForm = page.locator('div').filter({ hasText: 'JavaScript Sandbox' }).locator('..').first();
|
||||
const developerLabel = securityForm.locator('label').filter({ hasText: /^Developer Mode/ }).first();
|
||||
await developerLabel.waitFor({ state: 'visible', timeout: 5000 });
|
||||
await developerLabel.click();
|
||||
}
|
||||
|
||||
// Ensure Safe Mode radio is visible and check it
|
||||
await sandboxLocators.safeModeRadio().waitFor({ state: 'visible', timeout: 5000 });
|
||||
await sandboxLocators.safeModeRadio().check();
|
||||
}
|
||||
|
||||
await sandboxLocators.saveButton().click();
|
||||
await page.keyboard.press('Escape');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user