From 597ad1e277e9e49317a2a3c027c470da29c1a22d Mon Sep 17 00:00:00 2001 From: rocketraccoon Date: Sun, 23 Aug 2026 23:11:47 +0700 Subject: [PATCH 1/3] feat: stop tests button --- lib/gui/server.ts | 8 ++++ .../new-ui/components/MainLayout/hotkeys.ts | 1 + .../components/TreeActionsToolbar/index.tsx | 39 ++++++++++++++----- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/lib/gui/server.ts b/lib/gui/server.ts index d9f393b2c..a5af28631 100644 --- a/lib/gui/server.ts +++ b/lib/gui/server.ts @@ -44,6 +44,7 @@ export const start = async (args: ServerArgs): Promise => { const app = App.create(args); const server = express(); + let stopAll = false; server.use(bodyParser.json({limit: MAX_REQUEST_SIZE})); @@ -168,6 +169,12 @@ export const start = async (args: ServerArgs): Promise => { const {tests, repeatCount} = req.body; for (let i = 0; i < repeatCount; i++) { + if (stopAll) { + stopAll = false; + app.sendClientEvent(ClientEvents.REPEAT_LEFT, {repeatLeft: 0}); + break; + } + await app.run(tests, {retry: repeatCount === 1}); app.sendClientEvent(ClientEvents.REPEAT_LEFT, {repeatLeft: repeatCount - i - 1}); @@ -285,6 +292,7 @@ export const start = async (args: ServerArgs): Promise => { server.post('/stop', (_req, res) => { try { + stopAll = true; // pass 0 to prevent terminating testplane process toolAdapter.halt(new Error('Tests were stopped by the user'), 0); res.sendStatus(OK); diff --git a/lib/static/new-ui/components/MainLayout/hotkeys.ts b/lib/static/new-ui/components/MainLayout/hotkeys.ts index 1136f5a8c..83b8f695a 100644 --- a/lib/static/new-ui/components/MainLayout/hotkeys.ts +++ b/lib/static/new-ui/components/MainLayout/hotkeys.ts @@ -28,6 +28,7 @@ export const HOTKEYS_GROUPS: HotkeysGroup[] = [ {title: 'Next attempt', value: '→'}, {title: 'Run current test', value: 'r'}, {title: 'Run all/selected tests', value: 'shift+r'}, + {title: 'Stop all tests', value: 's'}, {title: 'Accept screenshot', value: 'a'}, {title: 'Undo accept', value: 'u'}, {title: 'Accept all/selected', value: 'shift+a'}, diff --git a/lib/static/new-ui/components/TreeActionsToolbar/index.tsx b/lib/static/new-ui/components/TreeActionsToolbar/index.tsx index 6093ab3f1..53acb8db1 100644 --- a/lib/static/new-ui/components/TreeActionsToolbar/index.tsx +++ b/lib/static/new-ui/components/TreeActionsToolbar/index.tsx @@ -11,6 +11,7 @@ import { Hierarchy, ListUl, Play, + Stop, Square, SquareCheck, SquareDashed, @@ -28,7 +29,8 @@ import { staticAccepterStageScreenshot, staticAccepterUnstageScreenshot, thunkRefreshGuiReport, - thunkRunTests + thunkRunTests, + thunkStopTests } from '@/static/modules/actions'; import {ImageEntity, TreeViewMode} from '@/static/new-ui/types/store'; import {CHECKED, INDETERMINATE} from '@/constants/checked-statuses'; @@ -163,6 +165,10 @@ export function TreeActionsToolbar({onHighlightCurrentTest, className}: TreeActi } }, [analytics, isSelectedAtLeastOne, selectedTests, visibleBrowserIds, browsersById, dispatch]); + const handleStop = useCallback((): void => { + dispatch(thunkStopTests()); + }, [thunkStopTests, dispatch]); + const handleUndo = (): void => { const acceptableImageIds = activeImages .filter(image => isScreenRevertable({image, gui: isGuiMode, isLastResult: true, isStaticImageAccepterEnabled})) @@ -197,6 +203,7 @@ export function TreeActionsToolbar({onHighlightCurrentTest, className}: TreeActi }; useHotkey('shift+r', handleRun, {enabled: Boolean(isRunTestsAvailable) && !isRunning && isInitialized}); + useHotkey('s', handleStop, {enabled: Boolean(isRunTestsAvailable) && isRunning && isInitialized}); useHotkey('shift+a', handleAccept, {enabled: Boolean(isEditScreensAvailable) && !areActionsDisabled && isAtLeastOneAcceptable && !isUndoButtonVisible}); const loadedPluginConfigs = plugins.getLoadedConfigs(); @@ -225,15 +232,27 @@ export function TreeActionsToolbar({onHighlightCurrentTest, className}: TreeActi /> )} {isRunTestsAvailable && ( - } - tooltip={<>Run {selectedOrVisible} ⋅ } - text="Run" - view={'flat'} - onClick={handleRun} - disabled={isRunning || !isInitialized} - /> + isRunning ? ( + } + tooltip={<>Stop all ⋅ } + text="Stop" + view={'flat'} + onClick={handleStop} + disabled={!isInitialized} + /> + ) : ( + } + tooltip={<>Run {selectedOrVisible} ⋅ } + text="Run" + view={'flat'} + onClick={handleRun} + disabled={!isInitialized} + /> + ) )} {isRunTestsAvailable && hasRunTestOptions && } From cea857c25e5b4b256f7a6e5f3af9e031ded3a7d5 Mon Sep 17 00:00:00 2001 From: rocketraccoon Date: Wed, 26 Aug 2026 04:19:22 +0700 Subject: [PATCH 2/3] fix: review --- lib/gui/server.ts | 1 + .../new-ui/components/MainLayout/hotkeys.ts | 2 +- .../components/RunTest/index.module.css | 7 +++++ .../new-ui/components/RunTest/index.tsx | 28 +++++++++++++++++-- .../components/TreeActionsToolbar/index.tsx | 4 +-- .../VisualChecksStickyHeader.tsx | 2 +- .../VisualChecksPage/index.module.css | 3 +- 7 files changed, 39 insertions(+), 8 deletions(-) diff --git a/lib/gui/server.ts b/lib/gui/server.ts index a5af28631..45643eab7 100644 --- a/lib/gui/server.ts +++ b/lib/gui/server.ts @@ -163,6 +163,7 @@ export const start = async (args: ServerArgs): Promise => { server.post('/run', async (req, res) => { try { + stopAll = false; // do not wait for completion so that response does not hang and browser does not restart it by timeout // eslint-disable-next-line @typescript-eslint/explicit-function-return-type (async () => { diff --git a/lib/static/new-ui/components/MainLayout/hotkeys.ts b/lib/static/new-ui/components/MainLayout/hotkeys.ts index 83b8f695a..96b2a48e8 100644 --- a/lib/static/new-ui/components/MainLayout/hotkeys.ts +++ b/lib/static/new-ui/components/MainLayout/hotkeys.ts @@ -28,7 +28,7 @@ export const HOTKEYS_GROUPS: HotkeysGroup[] = [ {title: 'Next attempt', value: '→'}, {title: 'Run current test', value: 'r'}, {title: 'Run all/selected tests', value: 'shift+r'}, - {title: 'Stop all tests', value: 's'}, + {title: 'Stop all tests', value: 'shift+s'}, {title: 'Accept screenshot', value: 'a'}, {title: 'Undo accept', value: 'u'}, {title: 'Accept all/selected', value: 'shift+a'}, diff --git a/lib/static/new-ui/components/RunTest/index.module.css b/lib/static/new-ui/components/RunTest/index.module.css index 7f7d66787..de04ef0e5 100644 --- a/lib/static/new-ui/components/RunTest/index.module.css +++ b/lib/static/new-ui/components/RunTest/index.module.css @@ -5,7 +5,14 @@ } .retry-button { + padding-right: 4px; +} + +.stop-button, .retry-button { composes: regular-button from global; +} + +.stop-button { padding-right: 4px; } diff --git a/lib/static/new-ui/components/RunTest/index.tsx b/lib/static/new-ui/components/RunTest/index.tsx index d0e139664..acac3aebb 100644 --- a/lib/static/new-ui/components/RunTest/index.tsx +++ b/lib/static/new-ui/components/RunTest/index.tsx @@ -1,9 +1,9 @@ import React, {forwardRef, ReactNode, useCallback, useState} from 'react'; import styles from './index.module.css'; -import {Button, ButtonProps, Icon, Popover} from '@gravity-ui/uikit'; -import {ArrowRotateRight, ChevronDown} from '@gravity-ui/icons'; -import {thunkRunTest} from '@/static/modules/actions'; +import {Button, ButtonProps, Icon, Popover, Hotkey} from '@gravity-ui/uikit'; +import {ArrowRotateRight, ChevronDown, Stop} from '@gravity-ui/icons'; +import {thunkRunTest, thunkStopTests} from '@/static/modules/actions'; import {useDispatch} from 'react-redux'; import {RunTestsFeature} from '@/constants'; import {useAnalytics} from '../../hooks/useAnalytics'; @@ -52,6 +52,28 @@ export const RunTestButton = forwardRef { + dispatch(thunkStopTests()); + }, [thunkStopTests, dispatch]); + + if (isRunning) { + return ( +
+ +
+ ); + } + return