Skip to content

Commit 6f0e2c7

Browse files
committed
Use Node config to disable delete session on UI
1 parent 1c97a2e commit 6f0e2c7

File tree

4 files changed

+66
-19
lines changed

4 files changed

+66
-19
lines changed

java/src/org/openqa/selenium/grid/node/config/NodeFlags.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import static org.openqa.selenium.grid.config.StandardGridRoles.NODE_ROLE;
2121
import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_CONNECTION_LIMIT;
22+
import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_DELETE_SESSION_ON_UI;
2223
import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_DETECT_DRIVERS;
2324
import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_DRAIN_AFTER_SESSION_COUNT;
2425
import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_ENABLE_BIDI;
@@ -241,6 +242,13 @@ public class NodeFlags implements HasRoles {
241242
@ConfigValue(section = NODE_SECTION, name = "drain-after-session-count", example = "1")
242243
public int drainAfterSessionCount = DEFAULT_DRAIN_AFTER_SESSION_COUNT;
243244

245+
@Parameter(
246+
names = {"--delete-session-on-ui"},
247+
arity = 1,
248+
description = "Enable capability to support deleting session on Grid UI. True by default")
249+
@ConfigValue(section = NODE_SECTION, name = "delete-session-on-ui", example = "true")
250+
public Boolean deleteSessionOnUi = DEFAULT_DELETE_SESSION_ON_UI;
251+
244252
@Parameter(
245253
names = {"--enable-cdp"},
246254
arity = 1,

java/src/org/openqa/selenium/grid/node/config/NodeOptions.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public class NodeOptions {
7777
public static final int DEFAULT_SESSION_TIMEOUT = 300;
7878
public static final int DEFAULT_DRAIN_AFTER_SESSION_COUNT = 0;
7979
public static final int DEFAULT_CONNECTION_LIMIT = 10;
80+
public static final boolean DEFAULT_DELETE_SESSION_ON_UI = true;
8081
public static final boolean DEFAULT_ENABLE_CDP = true;
8182
public static final boolean DEFAULT_ENABLE_BIDI = true;
8283
static final String NODE_SECTION = "node";
@@ -303,6 +304,13 @@ public int getDrainAfterSessionCount() {
303304
DEFAULT_DRAIN_AFTER_SESSION_COUNT);
304305
}
305306

307+
@VisibleForTesting
308+
boolean isSessionDeletedOnUi() {
309+
return config
310+
.getBool(NODE_SECTION, "delete-session-on-ui")
311+
.orElse(DEFAULT_DELETE_SESSION_ON_UI);
312+
}
313+
306314
@VisibleForTesting
307315
boolean isVncEnabled() {
308316
List<String> vncEnvVars = DEFAULT_VNC_ENV_VARS;
@@ -750,6 +758,10 @@ public Capabilities enhanceStereotype(Capabilities capabilities) {
750758
.setCapability("se:vncEnabled", true)
751759
.setCapability("se:noVncPort", noVncPort());
752760
}
761+
if (isSessionDeletedOnUi()) {
762+
capabilities =
763+
new PersistentCapabilities(capabilities).setCapability("se:deleteSessionOnUi", true);
764+
}
753765
if (isManagedDownloadsEnabled() && canConfigureDownloadsDir(capabilities)) {
754766
capabilities = new PersistentCapabilities(capabilities).setCapability(ENABLE_DOWNLOADS, true);
755767
}

javascript/grid-ui/src/components/RunningSessions/RunningSessions.tsx

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,6 @@ function RunningSessions (props) {
301301
} else {
302302
setRowOpen('')
303303
}
304-
} else if (response.status === 403) {
305-
setFeedbackMessage('Session deletion is blocked by configuration')
306-
setFeedbackSeverity('error')
307304
} else {
308305
setFeedbackMessage('Failed to delete session')
309306
setFeedbackSeverity('error')
@@ -377,6 +374,15 @@ function RunningSessions (props) {
377374
}
378375
}
379376

377+
const hasDeleteSessionCapability = (capabilitiesStr: string): boolean => {
378+
try {
379+
const capabilities = JSON.parse(capabilitiesStr as string)
380+
return capabilities['se:deleteSessionOnUi'] === true
381+
} catch (e) {
382+
return false
383+
}
384+
}
385+
380386
const rows = sessions.map((session) => {
381387
const sessionData = createSessionData(
382388
session.id,
@@ -614,14 +620,16 @@ function RunningSessions (props) {
614620
</Typography>
615621
</DialogContent>
616622
<DialogActions>
617-
<Button
618-
onClick={() => handleDeleteConfirmation(row.id as string, 'info')}
619-
color='error'
620-
variant='contained'
621-
sx={{ marginRight: 1 }}
622-
>
623-
Delete
624-
</Button>
623+
{hasDeleteSessionCapability(row.capabilities as string) && (
624+
<Button
625+
onClick={() => handleDeleteConfirmation(row.id as string, 'info')}
626+
color='error'
627+
variant='contained'
628+
sx={{ marginRight: 1 }}
629+
>
630+
Delete
631+
</Button>
632+
)}
625633
<Button
626634
onClick={() => setRowOpen('')}
627635
color='primary'
@@ -689,9 +697,6 @@ function RunningSessions (props) {
689697
<Typography>
690698
Are you sure you want to delete this session? This action cannot be undone.
691699
</Typography>
692-
<Typography variant="body2" color="text.secondary" sx={{ mt: 2, fontStyle: 'italic' }}>
693-
Hint: Set config `--blocked-delete-session` when starting Router/Hub to block deletion of any session.
694-
</Typography>
695700
</DialogContent>
696701
<DialogActions>
697702
<Button

javascript/grid-ui/src/tests/components/RunningSessions.test.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ const sessionWithWebSocketUrl: SessionInfo = {
101101
"se:cdp": "ws://localhost:4444/selenium/session/2103faaea8600e41a1e86f4189779e66/se/cdp",
102102
"se:cdpVersion": "136.0.7103.113",
103103
"se:containerName": "0ca4ada66da5",
104+
"se:deleteSessionOnUi": true,
104105
"se:downloadsEnabled": true,
105106
"se:gridWebSocketUrl": "ws://localhost:4444/selenium/session/2103faaea8600e41a1e86f4189779e66",
106107
"se:noVncPort": 7900,
@@ -139,7 +140,8 @@ const sessionWithoutWebSocketUrl: SessionInfo = {
139140
capabilities: JSON.stringify({
140141
"browserName": "chrome",
141142
"browserVersion": "88.0.4324.182",
142-
"platformName": "windows"
143+
"platformName": "windows",
144+
"se:deleteSessionOnUi": true
143145
}),
144146
startTime: '27/05/2025 13:13:05',
145147
uri: 'http://localhost:4444',
@@ -286,7 +288,7 @@ describe('Session deletion functionality', () => {
286288
expect(screen.getByText('Are you sure you want to delete this session? This action cannot be undone.')).toBeInTheDocument()
287289

288290
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument()
289-
expect(screen.getByRole('button', { name: /delete/i, exact: true })).toBeInTheDocument()
291+
expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument()
290292
})
291293

292294
it('uses window.location.origin for URL construction with se:gridWebSocketUrl', async () => {
@@ -302,7 +304,7 @@ describe('Session deletion functionality', () => {
302304
const deleteButton = screen.getByRole('button', { name: /delete/i })
303305
await user.click(deleteButton)
304306

305-
const confirmButton = screen.getByRole('button', { name: /delete/i, exact: true })
307+
const confirmButton = screen.getByRole('button', { name: /delete/i })
306308
await user.click(confirmButton)
307309

308310
expect(global.fetch).toHaveBeenCalledWith(
@@ -329,7 +331,7 @@ describe('Session deletion functionality', () => {
329331
const deleteButton = screen.getByRole('button', { name: /delete/i })
330332
await user.click(deleteButton)
331333

332-
const confirmButton = screen.getByRole('button', { name: /delete/i, exact: true })
334+
const confirmButton = screen.getByRole('button', { name: /delete/i })
333335
await user.click(confirmButton)
334336

335337
const expectedUrl = window.location.href.split('/ui')[0] + '/session/' + sessionWithoutWsData.id
@@ -358,7 +360,7 @@ describe('Session deletion functionality', () => {
358360
const deleteButton = screen.getByRole('button', { name: /delete/i })
359361
await user.click(deleteButton)
360362

361-
const confirmButton = screen.getByRole('button', { name: /delete/i, exact: true })
363+
const confirmButton = screen.getByRole('button', { name: /delete/i })
362364
await user.click(confirmButton)
363365

364366
await waitFor(() => {
@@ -389,4 +391,24 @@ describe('Session deletion functionality', () => {
389391

390392
expect(global.fetch).not.toHaveBeenCalled()
391393
})
394+
395+
it('does not show delete button when session does not have se:deleteSessionOnUi capability', async () => {
396+
const sessionWithoutDeleteCapability = {
397+
...sessionWithWsData,
398+
capabilities: JSON.stringify({
399+
"browserName": "chrome",
400+
"browserVersion": "136.0.7103.113",
401+
"platformName": "linux"
402+
})
403+
}
404+
405+
render(<RunningSessions sessions={[sessionWithoutDeleteCapability]} origin={origin} />)
406+
407+
const user = userEvent.setup()
408+
const sessionRow = screen.getByText(sessionWithoutDeleteCapability.id).closest('tr')
409+
410+
await user.click(within(sessionRow as HTMLElement).getByTestId('InfoIcon'))
411+
412+
expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument()
413+
})
392414
})

0 commit comments

Comments
 (0)