From 9962eb5c6ef979e957002cc1d104d418bb98a36f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 17:48:11 +0530 Subject: [PATCH 01/20] feat: set margin top --- src/components/robot/pages/RobotEditPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 3f17560c9..0101d5dc2 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -506,7 +506,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { return ( <> - + {t("List Limits")} From 7e0ea386355ea6c223289334deacb8b5961d78d2 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 17:48:55 +0530 Subject: [PATCH 02/20] feat: set margin top --- src/components/robot/pages/RobotEditPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 0101d5dc2..e85b81fb9 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -571,7 +571,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { return ( <> - + {t('Actions')} {inputs} From 158347bf90be2110af2c3356d60ca09bcbe2cc4e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 17:58:28 +0530 Subject: [PATCH 03/20] feat: group actions by type --- src/components/robot/pages/RobotEditPage.tsx | 115 +++++++++++++------ 1 file changed, 77 insertions(+), 38 deletions(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index e85b81fb9..7d3cee74b 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -536,48 +536,87 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { }; const renderActionNameFields = () => { - if (!robot || !robot.recording || !robot.recording.workflow) return null; - - const editableActions = new Set(['screenshot', 'scrapeList', 'scrapeSchema']); - const inputs: JSX.Element[] = []; - - robot.recording.workflow.forEach((pair, pairIndex) => { - if (!pair.what) return; - - pair.what.forEach((action, actionIndex) => { - // Only show editable name inputs for meaningful action types - if (!editableActions.has(String(action.action))) return; - - // derive current name from possible fields - const currentName = - action.name || - (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || - ''; + if (!robot || !robot.recording || !robot.recording.workflow) return null; + + const editableActions = new Set(['screenshot', 'scrapeList', 'scrapeSchema']); + const textInputs: JSX.Element[] = []; + const screenshotInputs: JSX.Element[] = []; + const listInputs: JSX.Element[] = []; + + robot.recording.workflow.forEach((pair, pairIndex) => { + if (!pair.what) return; + + pair.what.forEach((action, actionIndex) => { + if (!editableActions.has(String(action.action))) return; + + const currentName = + action.name || + (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || + ''; + + const textField = ( + handleActionNameChange(pairIndex, actionIndex, e.target.value)} + style={{ marginBottom: '12px' }} + fullWidth + /> + ); - inputs.push( - handleActionNameChange(pairIndex, actionIndex, e.target.value)} - style={{ marginBottom: '12px' }} - fullWidth - /> - ); - }); + switch (action.action) { + case 'scrapeSchema': + textInputs.push(textField); + break; + case 'screenshot': + screenshotInputs.push(textField); + break; + case 'scrapeList': + listInputs.push(textField); + break; + } }); + }); - if (inputs.length === 0) return null; + const hasAnyInputs = textInputs.length > 0 || screenshotInputs.length > 0 || listInputs.length > 0; + if (!hasAnyInputs) return null; - return ( - <> - - {t('Actions')} - - {inputs} - - ); - }; + return ( + <> + + {t('Actions')} + + + {textInputs.length > 0 && ( + <> + + Texts + + {textInputs} + + )} + + {screenshotInputs.length > 0 && ( + <> + 0 ? '16px' : '0' }}> + Screenshots + + {screenshotInputs} + + )} + + {listInputs.length > 0 && ( + <> + 0 || screenshotInputs.length > 0) ? '16px' : '0' }}> + Lists + + {listInputs} + + )} + + ); +}; const renderCredentialFields = ( selectors: string[], From e6d3323236c59ceb91d568646e5068ff9c896e10 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 17:58:40 +0530 Subject: [PATCH 04/20] chore: lint --- src/components/robot/pages/RobotEditPage.tsx | 154 +++++++++---------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 7d3cee74b..6b323c6ec 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -536,87 +536,87 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { }; const renderActionNameFields = () => { - if (!robot || !robot.recording || !robot.recording.workflow) return null; - - const editableActions = new Set(['screenshot', 'scrapeList', 'scrapeSchema']); - const textInputs: JSX.Element[] = []; - const screenshotInputs: JSX.Element[] = []; - const listInputs: JSX.Element[] = []; - - robot.recording.workflow.forEach((pair, pairIndex) => { - if (!pair.what) return; - - pair.what.forEach((action, actionIndex) => { - if (!editableActions.has(String(action.action))) return; - - const currentName = - action.name || - (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || - ''; - - const textField = ( - handleActionNameChange(pairIndex, actionIndex, e.target.value)} - style={{ marginBottom: '12px' }} - fullWidth - /> - ); + if (!robot || !robot.recording || !robot.recording.workflow) return null; - switch (action.action) { - case 'scrapeSchema': - textInputs.push(textField); - break; - case 'screenshot': - screenshotInputs.push(textField); - break; - case 'scrapeList': - listInputs.push(textField); - break; - } + const editableActions = new Set(['screenshot', 'scrapeList', 'scrapeSchema']); + const textInputs: JSX.Element[] = []; + const screenshotInputs: JSX.Element[] = []; + const listInputs: JSX.Element[] = []; + + robot.recording.workflow.forEach((pair, pairIndex) => { + if (!pair.what) return; + + pair.what.forEach((action, actionIndex) => { + if (!editableActions.has(String(action.action))) return; + + const currentName = + action.name || + (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || + ''; + + const textField = ( + handleActionNameChange(pairIndex, actionIndex, e.target.value)} + style={{ marginBottom: '12px' }} + fullWidth + /> + ); + + switch (action.action) { + case 'scrapeSchema': + textInputs.push(textField); + break; + case 'screenshot': + screenshotInputs.push(textField); + break; + case 'scrapeList': + listInputs.push(textField); + break; + } + }); }); - }); - const hasAnyInputs = textInputs.length > 0 || screenshotInputs.length > 0 || listInputs.length > 0; - if (!hasAnyInputs) return null; + const hasAnyInputs = textInputs.length > 0 || screenshotInputs.length > 0 || listInputs.length > 0; + if (!hasAnyInputs) return null; - return ( - <> - - {t('Actions')} - - - {textInputs.length > 0 && ( - <> - - Texts - - {textInputs} - - )} - - {screenshotInputs.length > 0 && ( - <> - 0 ? '16px' : '0' }}> - Screenshots - - {screenshotInputs} - - )} - - {listInputs.length > 0 && ( - <> - 0 || screenshotInputs.length > 0) ? '16px' : '0' }}> - Lists - - {listInputs} - - )} - - ); -}; + return ( + <> + + {t('Actions')} + + + {textInputs.length > 0 && ( + <> + + Texts + + {textInputs} + + )} + + {screenshotInputs.length > 0 && ( + <> + 0 ? '16px' : '0' }}> + Screenshots + + {screenshotInputs} + + )} + + {listInputs.length > 0 && ( + <> + 0 || screenshotInputs.length > 0) ? '16px' : '0' }}> + Lists + + {listInputs} + + )} + + ); + }; const renderCredentialFields = ( selectors: string[], From 6692de2b130fc1523870b38ceb0f84ef6f154805 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 17:59:58 +0530 Subject: [PATCH 05/20] fix: use h6 typography --- src/components/robot/pages/RobotEditPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 6b323c6ec..1eabc5d43 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -584,7 +584,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { return ( <> - + {t('Actions')} From d5e250001788b614ebfb22549ff1ae2aa697335a Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 18:09:21 +0530 Subject: [PATCH 06/20] fix: use h6 typography --- src/components/robot/pages/RobotEditPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 1eabc5d43..96f7c9d3f 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -506,7 +506,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { return ( <> - + {t("List Limits")} From f5e9cec74f07e0e39c879efa567d36b30505ef9d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 27 Oct 2025 18:09:38 +0530 Subject: [PATCH 07/20] fix: remove maxHeight --- src/components/robot/pages/RobotConfigPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/robot/pages/RobotConfigPage.tsx b/src/components/robot/pages/RobotConfigPage.tsx index 53ae90abe..d52b985e5 100644 --- a/src/components/robot/pages/RobotConfigPage.tsx +++ b/src/components/robot/pages/RobotConfigPage.tsx @@ -62,7 +62,6 @@ export const RobotConfigPage: React.FC = ({ Date: Mon, 27 Oct 2025 18:40:44 +0530 Subject: [PATCH 08/20] fix: add default names for captured action data --- src/components/robot/pages/RobotEditPage.tsx | 63 +++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 96f7c9d3f..5aa04b7ab 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -543,17 +543,50 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { const screenshotInputs: JSX.Element[] = []; const listInputs: JSX.Element[] = []; + let textCount = 0; + let screenshotCount = 0; + let listCount = 0; + robot.recording.workflow.forEach((pair, pairIndex) => { if (!pair.what) return; pair.what.forEach((action, actionIndex) => { if (!editableActions.has(String(action.action))) return; - const currentName = + let currentName = action.name || (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || ''; + if (!currentName) { + switch (action.action) { + case 'scrapeSchema': + textCount++; + currentName = `Text ${textCount}`; + break; + case 'screenshot': + screenshotCount++; + currentName = `Screenshot ${screenshotCount}`; + break; + case 'scrapeList': + listCount++; + currentName = `List ${listCount}`; + break; + } + } else { + switch (action.action) { + case 'scrapeSchema': + textCount++; + break; + case 'screenshot': + screenshotCount++; + break; + case 'scrapeList': + listCount++; + break; + } + } + const textField = ( { }); }); + if (textInputs.length === 1 && textCount === 1) { + robot.recording.workflow.forEach((pair, pairIndex) => { + if (!pair.what) return; + + pair.what.forEach((action, actionIndex) => { + if (action.action === 'scrapeSchema') { + const existingName = + action.name || + (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || + ''; + + const currentName = !existingName ? 'Texts' : existingName; + + textInputs[0] = ( + handleActionNameChange(pairIndex, actionIndex, e.target.value)} + style={{ marginBottom: '12px' }} + fullWidth + /> + ); + } + }); + }); + } + const hasAnyInputs = textInputs.length > 0 || screenshotInputs.length > 0 || listInputs.length > 0; if (!hasAnyInputs) return null; From b9ab9bd7c99e8436bc1119770afc323075618800 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Mon, 27 Oct 2025 18:59:11 +0530 Subject: [PATCH 09/20] fix: add null checks legacy data --- src/components/run/RunContent.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/run/RunContent.tsx b/src/components/run/RunContent.tsx index ee397bf42..4cc787d14 100644 --- a/src/components/run/RunContent.tsx +++ b/src/components/run/RunContent.tsx @@ -87,8 +87,8 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const hasOldFormat = !row.serializableOutput.scrapeSchema && !row.serializableOutput.scrapeList && Object.keys(row.serializableOutput).length > 0; if (hasLegacySchema || hasLegacyList || hasOldFormat) { - setIsLegacyData(true); processLegacyData(row.serializableOutput); + setIsLegacyData(false); return; } @@ -154,11 +154,12 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const data = legacyOutput[key]; if (Array.isArray(data)) { - const isNestedArray = data.length > 0 && Array.isArray(data[0]); + const firstNonNullElement = data.find(item => item !== null && item !== undefined); + const isNestedArray = firstNonNullElement && Array.isArray(firstNonNullElement); if (isNestedArray) { data.forEach((subArray, index) => { - if (Array.isArray(subArray) && subArray.length > 0) { + if (subArray !== null && subArray !== undefined && Array.isArray(subArray) && subArray.length > 0) { const filteredData = subArray.filter(row => row && typeof row === 'object' && Object.values(row).some(value => value !== undefined && value !== "") ); @@ -171,7 +172,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe }); } else { const filteredData = data.filter(row => - row && typeof row === 'object' && Object.values(row).some(value => value !== undefined && value !== "") + row && typeof row === 'object' && !Array.isArray(row) && Object.values(row).some(value => value !== undefined && value !== "") ); if (filteredData.length > 0) { @@ -208,7 +209,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe if (Array.isArray(schemaOutput)) { const filteredData = schemaOutput.filter(row => - row && Object.values(row).some(value => value !== undefined && value !== "") + row && typeof row === 'object' && Object.values(row).some(value => value !== undefined && value !== "") ); if (filteredData.length > 0) { @@ -231,7 +232,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const data = schemaOutput[key]; if (Array.isArray(data)) { const filteredData = data.filter(row => - Object.values(row).some(value => value !== undefined && value !== "") + row && typeof row === 'object' && Object.values(row).some(value => value !== undefined && value !== "") ); dataByKey[key] = filteredData; @@ -272,7 +273,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe const tableData = scrapeListData[key]; if (Array.isArray(tableData) && tableData.length > 0) { const filteredData = tableData.filter(row => - Object.values(row).some(value => value !== undefined && value !== "") + row && typeof row === 'object' && Object.values(row).some(value => value !== undefined && value !== "") ); if (filteredData.length > 0) { tablesList.push(filteredData); From f532a6ae2e8fef5f6cc0d3c88a0bdca99a9f54ee Mon Sep 17 00:00:00 2001 From: saniyafatima07 Date: Wed, 29 Oct 2025 12:35:12 +0530 Subject: [PATCH 10/20] Feat: Implemented delete confirmation modal --- src/components/robot/RecordingsTable.tsx | 71 +++++++++++++++++++----- src/components/run/ColapsibleRow.tsx | 49 +++++++++++++--- 2 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/components/robot/RecordingsTable.tsx b/src/components/robot/RecordingsTable.tsx index 504cfa434..f06270ed3 100644 --- a/src/components/robot/RecordingsTable.tsx +++ b/src/components/robot/RecordingsTable.tsx @@ -42,6 +42,7 @@ import { Add } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { canCreateBrowserInState, getActiveBrowserId, stopRecording } from "../../api/recording"; import { GenericModal } from '../ui/GenericModal'; +import { useTheme } from '@mui/material/styles'; declare global { interface Window { @@ -148,12 +149,15 @@ export const RecordingsTable = ({ handleEditRobot, handleDuplicateRobot }: RecordingsTableProps) => { const { t } = useTranslation(); + const theme = useTheme(); const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); const { data: recordingsData = [], isLoading: isFetching, error, refetch } = useCachedRecordings(); const [isModalOpen, setModalOpen] = React.useState(false); const [searchTerm, setSearchTerm] = React.useState(''); const [isWarningModalOpen, setWarningModalOpen] = React.useState(false); + const [isDeleteConfirmOpen, setDeleteConfirmOpen] = React.useState(false); + const [pendingDeleteId, setPendingDeleteId] = React.useState(null); const [activeBrowserId, setActiveBrowserId] = React.useState(''); const columns = useMemo(() => [ @@ -431,6 +435,32 @@ export const RecordingsTable = ({ return filteredRows.slice(start, start + rowsPerPage); }, [filteredRows, page, rowsPerPage]); + const openDeleteConfirm = React.useCallback((id: string) => { + setPendingDeleteId(String(id)); + setDeleteConfirmOpen(true); + }, []); + + const confirmDeleteRecording = React.useCallback(async () => { + if (!pendingDeleteId) return; + const hasRuns = await checkRunsForRecording(pendingDeleteId); + if (hasRuns) { + notify('warning', t('recordingtable.notifications.delete_warning')); + setDeleteConfirmOpen(false); + setPendingDeleteId(null); + return; + } + + const success = await deleteRecordingFromStorage(pendingDeleteId); + if (success) { + notify('success', t('recordingtable.notifications.delete_success')); + refetch(); + } + setDeleteConfirmOpen(false); + setPendingDeleteId(null); + }, [pendingDeleteId, notify, t, refetch]); + + const pendingRow = pendingDeleteId ? rows.find(r => String(r.id) === pendingDeleteId) : null; + const handlers = useMemo(() => ({ handleRunRecording, handleScheduleRecording, @@ -439,19 +469,7 @@ export const RecordingsTable = ({ handleEditRobot, handleDuplicateRobot, handleRetrainRobot, - handleDelete: async (id: string) => { - const hasRuns = await checkRunsForRecording(id); - if (hasRuns) { - notify('warning', t('recordingtable.notifications.delete_warning')); - return; - } - - const success = await deleteRecordingFromStorage(id); - if (success) { - notify('success', t('recordingtable.notifications.delete_success')); - refetch(); - } - } + handleDelete: async (id: string) => openDeleteConfirm(id) }), [handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot, handleDuplicateRobot, handleRetrainRobot, notify, t, refetch]); return ( @@ -628,6 +646,33 @@ export const RecordingsTable = ({ + { setDeleteConfirmOpen(false); setPendingDeleteId(null); }} + modalStyle={{ ...modalStyle, padding: 0, backgroundColor: 'transparent', width: 'auto', maxWidth: '520px' }} + > + + + + {t('recordingtable.delete_confirm.title', { name: pendingRow?.name, defaultValue: 'Delete {{name}}?' })} + + + {t('recordingtable.delete_confirm.message', { + name: pendingRow?.name, + defaultValue: 'Are you sure you want to delete the robot "{{name}}"?' + })} + + + + + + + + ); } diff --git a/src/components/run/ColapsibleRow.tsx b/src/components/run/ColapsibleRow.tsx index bf6e9c405..63025a465 100644 --- a/src/components/run/ColapsibleRow.tsx +++ b/src/components/run/ColapsibleRow.tsx @@ -3,6 +3,7 @@ import * as React from "react"; import TableRow from "@mui/material/TableRow"; import TableCell from "@mui/material/TableCell"; import { Box, Collapse, IconButton, Typography, Chip, TextField } from "@mui/material"; +import { Button } from "@mui/material"; import { DeleteForever, KeyboardArrowDown, KeyboardArrowUp, Settings } from "@mui/icons-material"; import { deleteRunFromStorage } from "../../api/storage"; import { columns, Data } from "./RunsTable"; @@ -11,6 +12,7 @@ import { GenericModal } from "../ui/GenericModal"; import { modalStyle } from "../recorder/AddWhereCondModal"; import { getUserById } from "../../api/auth"; import { useTranslation } from "react-i18next"; +import { useTheme } from "@mui/material/styles"; interface RunTypeChipProps { runByUserId?: string; @@ -39,6 +41,8 @@ interface CollapsibleRowProps { } export const CollapsibleRow = ({ row, handleDelete, isOpen, onToggleExpanded, currentLog, abortRunHandler, runningRecordingName, urlRunId }: CollapsibleRowProps) => { const { t } = useTranslation(); + const theme = useTheme(); + const [isDeleteOpen, setDeleteOpen] = useState(false); const [openSettingsModal, setOpenSettingsModal] = useState(false); const [userEmail, setUserEmail] = useState(null); const runByLabel = row.runByScheduleId @@ -83,6 +87,17 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, onToggleExpanded, cu fetchUserEmail(); }, [row.runByUserId]); + const handleConfirmDelete = async () => { + try { + const res = await deleteRunFromStorage(`${row.runId}`); + if (res) { + handleDelete(); + } + } finally { + setDeleteOpen(false); + } + }; + return ( *': { borderBottom: 'unset' } }} hover role="checkbox" tabIndex={-1} key={row.id}> @@ -120,13 +135,7 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, onToggleExpanded, cu case 'delete': return ( - { - deleteRunFromStorage(`${row.runId}`).then((result: boolean) => { - if (result) { - handleDelete(); - } - }) - }}> + setDeleteOpen(true)}> @@ -192,6 +201,32 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, onToggleExpanded, cu + + setDeleteOpen(false)} modalStyle={{ ...modalStyle, padding: 0, backgroundColor: 'transparent', width: 'auto', maxWidth: '520px' }}> + + + {t('runs_table.delete_confirm.title', { + name: row.name, + defaultValue: 'Delete run "{{name}}"?' + })} + + + {t('runs_table.delete_confirm.message', { + name: row.name, + defaultValue: 'Are you sure you want to delete the run "{{name}}"?' + })} + + + + + + + ); } From f39195a11c7f5e051139ab8a2d0065fc3f8b3092 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Wed, 29 Oct 2025 15:06:06 +0530 Subject: [PATCH 11/20] fix: check text content for meaningful logic --- src/helpers/clientSelectorGenerator.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/helpers/clientSelectorGenerator.ts b/src/helpers/clientSelectorGenerator.ts index 03ad67a23..3189a4303 100644 --- a/src/helpers/clientSelectorGenerator.ts +++ b/src/helpers/clientSelectorGenerator.ts @@ -564,16 +564,17 @@ class ClientSelectorGenerator { return true; } - if (element.children.length > 0) { - return false; - } - const text = (element.textContent || "").trim(); + const hasVisibleText = text.length > 0; - if (text.length > 0) { + if (hasVisibleText || element.querySelector("svg")) { return true; } + if (element.children.length > 0) { + return false; + } + return false; } From 377dde56edab6b3de9504aafdea9b86b65c9f6b6 Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 01:28:59 +0530 Subject: [PATCH 12/20] chore: create setup.md Added detailed setup instructions for local installation, including Docker and non-Docker methods, as well as environment variable configurations. --- SETUP.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 SETUP.md diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 000000000..da89e75a0 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,76 @@ +# Local Installation +1. Create a root folder for your project (e.g. 'maxun') +2. Create a file named `.env` in the root folder of the project +3. Example env file can be viewed [here](https://github.com/getmaxun/maxun/blob/master/ENVEXAMPLE). Copy all content of example env to your `.env` file. +4. Choose your installation method below + +### Docker Compose +1. Copy paste the [docker-compose.yml file](https://github.com/getmaxun/maxun/blob/master/docker-compose.yml) into your root folder +2. Ensure you have setup the `.env` file in that same folder +3. Run the command below from a terminal +``` +docker-compose up -d +``` +You can access the frontend at http://localhost:5173/ and backend at http://localhost:8080/ + +### Without Docker +1. Ensure you have Node.js, PostgreSQL, MinIO and Redis installed on your system. +2. Run the commands below +``` +git clone https://github.com/getmaxun/maxun + +# change directory to the project root +cd maxun + +# install dependencies +npm install + +# change directory to maxun-core to install dependencies +cd maxun-core +npm install + +# get back to the root directory +cd .. + +# install chromium and its dependencies +npx playwright install --with-deps chromium + +# get back to the root directory +cd .. + +# start frontend and backend together +npm run start +``` +You can access the frontend at http://localhost:5173/ and backend at http://localhost:8080/ + + +# Environment Variables +1. Create a file named `.env` in the root folder of the project +2. Example env file can be viewed [here](https://github.com/getmaxun/maxun/blob/master/ENVEXAMPLE). + +| Variable | Mandatory | Description | If Not Set | +|-----------------------|-----------|----------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| `BACKEND_PORT` | Yes | Port to run backend on. Needed for Docker setup | Default value: 8080 | +| `FRONTEND_PORT` | Yes | Port to run frontend on. Needed for Docker setup | Default value: 5173 | +| `BACKEND_URL` | Yes | URL to run backend on. | Default value: http://localhost:8080 | +| `VITE_BACKEND_URL` | Yes | URL used by frontend to connect to backend | Default value: http://localhost:8080 | +| `PUBLIC_URL` | Yes | URL to run frontend on. | Default value: http://localhost:5173 | +| `VITE_PUBLIC_URL` | Yes | URL used by backend to connect to frontend | Default value: http://localhost:5173 | +| `JWT_SECRET` | Yes | Secret key used to sign and verify JSON Web Tokens (JWTs) for authentication. | JWT authentication will not work. | +| `DB_NAME` | Yes | Name of the Postgres database to connect to. | Database connection will fail. | +| `DB_USER` | Yes | Username for Postgres database authentication. | Database connection will fail. | +| `DB_PASSWORD` | Yes | Password for Postgres database authentication. | Database connection will fail. | +| `DB_HOST` | Yes | Host address where the Postgres database server is running. | Database connection will fail. | +| `DB_PORT` | Yes | Port number used to connect to the Postgres database server. | Database connection will fail. | +| `ENCRYPTION_KEY` | Yes | Key used for encrypting sensitive data (proxies, passwords). | Encryption functionality will not work. | +| `SESSION_SECRET` | No | A strong, random string used to sign session cookies | Uses default secret. Recommended to define your own session secret to avoid session hijacking. | +| `MINIO_ENDPOINT` | Yes | Endpoint URL for MinIO, to store Robot Run Screenshots. | Connection to MinIO storage will fail. | +| `MINIO_PORT` | Yes | Port number for MinIO service. | Connection to MinIO storage will fail. | +| `MINIO_CONSOLE_PORT` | No | Port number for MinIO WebUI service. Needed for Docker setup. | Cannot access MinIO Web UI. | +| `MINIO_ACCESS_KEY` | Yes | Access key for authenticating with MinIO. | MinIO authentication will fail. | +| `GOOGLE_CLIENT_ID` | No | Client ID for Google OAuth. Used for Google Sheet integration authentication. | Google login will not work. | +| `GOOGLE_CLIENT_SECRET`| No | Client Secret for Google OAuth. Used for Google Sheet integration authentication. | Google login will not work. | +| `GOOGLE_REDIRECT_URI` | No | Redirect URI for handling Google OAuth responses. | Google login will not work. | +| `AIRTABLE_CLIENT_ID` | No | Client ID for Airtable, used for Airtable integration authentication. | Airtable login will not work. | +| `AIRTABLE_REDIRECT_URI` | No | Redirect URI for handling Airtable OAuth responses. | Airtable login will not work. | +| `MAXUN_TELEMETRY` | No | Disables telemetry to stop sending anonymous usage data. Keeping it enabled helps us understand how the product is used and assess the impact of any new changes. Please keep it enabled. | Telemetry data will not be collected. | From b17159f083a8075905650661a34974e8fc92fba5 Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 02:44:16 +0530 Subject: [PATCH 13/20] chore: cleaner setup instructions + demo video --- README.md | 154 +++++++++++++++++------------------------------------- 1 file changed, 47 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 72d4eccb0..49fe260b0 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -

+

- Open-Source No-Code Web Data Extraction Platform
-

+ The Easiest Way To Extract Web Data With No Code
+

Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web data extraction doesn't get easier than this! @@ -15,115 +15,55 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web

- Go To App | - Documentation | - Website | - Discord | - Twitter | + Go To App • + Documentation • + Website • + DiscordWatch Tutorials

getmaxun%2Fmaxun | Trendshift

-![maxun_gif](https://github.com/user-attachments/assets/3e0b0cf8-9e52-44d2-a140-b26b7b481477) +https://github.com/user-attachments/assets/c6baa75f-b950-482c-8d26-8a8b6c5382c3 -# Getting Started -The simplest & fastest way to get started is to use the hosted version: https://app.maxun.dev. Maxun Cloud deals with anti-bot detection, huge proxy network with automatic proxy rotation, and CAPTCHA solving. - -# Local Installation -1. Create a root folder for your project (e.g. 'maxun') -2. Create a file named `.env` in the root folder of the project -3. Example env file can be viewed [here](https://github.com/getmaxun/maxun/blob/master/ENVEXAMPLE). Copy all content of example env to your `.env` file. -4. Choose your installation method below - -### Docker Compose -1. Copy paste the [docker-compose.yml file](https://github.com/getmaxun/maxun/blob/master/docker-compose.yml) into your root folder -2. Ensure you have setup the `.env` file in that same folder -3. Run the command below from a terminal -``` -docker-compose up -d -``` -You can access the frontend at http://localhost:5173/ and backend at http://localhost:8080/ - -### Without Docker -1. Ensure you have Node.js, PostgreSQL, MinIO and Redis installed on your system. -2. Run the commands below -``` -git clone https://github.com/getmaxun/maxun - -# change directory to the project root -cd maxun - -# install dependencies -npm install - -# change directory to maxun-core to install dependencies -cd maxun-core -npm install - -# get back to the root directory -cd .. - -# install chromium and its dependencies -npx playwright install --with-deps chromium - -# get back to the root directory -cd .. - -# start frontend and backend together -npm run start -``` -You can access the frontend at http://localhost:5173/ and backend at http://localhost:8080/ - - -# Environment Variables -1. Create a file named `.env` in the root folder of the project -2. Example env file can be viewed [here](https://github.com/getmaxun/maxun/blob/master/ENVEXAMPLE). - -| Variable | Mandatory | Description | If Not Set | -|-----------------------|-----------|----------------------------------------------------------------------------------------------|--------------------------------------------------------------| -| `BACKEND_PORT` | Yes | Port to run backend on. Needed for Docker setup | Default value: 8080 | -| `FRONTEND_PORT` | Yes | Port to run frontend on. Needed for Docker setup | Default value: 5173 | -| `BACKEND_URL` | Yes | URL to run backend on. | Default value: http://localhost:8080 | -| `VITE_BACKEND_URL` | Yes | URL used by frontend to connect to backend | Default value: http://localhost:8080 | -| `PUBLIC_URL` | Yes | URL to run frontend on. | Default value: http://localhost:5173 | -| `VITE_PUBLIC_URL` | Yes | URL used by backend to connect to frontend | Default value: http://localhost:5173 | -| `JWT_SECRET` | Yes | Secret key used to sign and verify JSON Web Tokens (JWTs) for authentication. | JWT authentication will not work. | -| `DB_NAME` | Yes | Name of the Postgres database to connect to. | Database connection will fail. | -| `DB_USER` | Yes | Username for Postgres database authentication. | Database connection will fail. | -| `DB_PASSWORD` | Yes | Password for Postgres database authentication. | Database connection will fail. | -| `DB_HOST` | Yes | Host address where the Postgres database server is running. | Database connection will fail. | -| `DB_PORT` | Yes | Port number used to connect to the Postgres database server. | Database connection will fail. | -| `ENCRYPTION_KEY` | Yes | Key used for encrypting sensitive data (proxies, passwords). | Encryption functionality will not work. | -| `SESSION_SECRET` | No | A strong, random string used to sign session cookies | Uses default secret. Recommended to define your own session secret to avoid session hijacking. | -| `MINIO_ENDPOINT` | Yes | Endpoint URL for MinIO, to store Robot Run Screenshots. | Connection to MinIO storage will fail. | -| `MINIO_PORT` | Yes | Port number for MinIO service. | Connection to MinIO storage will fail. | -| `MINIO_CONSOLE_PORT` | No | Port number for MinIO WebUI service. Needed for Docker setup. | Cannot access MinIO Web UI. | -| `MINIO_ACCESS_KEY` | Yes | Access key for authenticating with MinIO. | MinIO authentication will fail. | -| `GOOGLE_CLIENT_ID` | No | Client ID for Google OAuth. Used for Google Sheet integration authentication. | Google login will not work. | -| `GOOGLE_CLIENT_SECRET`| No | Client Secret for Google OAuth. Used for Google Sheet integration authentication. | Google login will not work. | -| `GOOGLE_REDIRECT_URI` | No | Redirect URI for handling Google OAuth responses. | Google login will not work. | -| `AIRTABLE_CLIENT_ID` | No | Client ID for Airtable, used for Airtable integration authentication. | Airtable login will not work. | -| `AIRTABLE_REDIRECT_URI` | No | Redirect URI for handling Airtable OAuth responses. | Airtable login will not work. | -| `MAXUN_TELEMETRY` | No | Disables telemetry to stop sending anonymous usage data. Keeping it enabled helps us understand how the product is used and assess the impact of any new changes. Please keep it enabled. | Telemetry data will not be collected. | - -# How Do I Self-Host? -Checkout community self hosting guide: https://docs.maxun.dev/self-host - -# How Does It Work? -Maxun lets you create custom robots which emulate user actions and extract data. A robot can perform any of the actions: Capture List, Capture Text or Capture Screenshot. Once a robot is created, it will keep extracting data for you without manual intervention - -![Screenshot 2024-10-23 222138](https://github.com/user-attachments/assets/53573c98-769e-490d-829e-ada9fac0764f) - -## 1. Robot Actions -1. Capture List: Useful to extract structured and bulk items from the website. Example: Scrape products from Amazon etc. +### Getting Started +The simplest & fastest way to get started is to use the hosted version: https://app.maxun.dev. You can self-host if you like! + +### Installation +Maxun can run locally with or without Docker +1. [Setup with Docker Compose](https://docs.maxun.dev/installation/docker) +2. [Setup without Docker](https://docs.maxun.dev/installation/local) +3. [Environment Variables](https://docs.maxun.dev/installation/environment_variables) + +### Upgrading & Self Hosting +1. [Self Host Maxun With Docker & Portainer](https://docs.maxun.dev/self-host) +2. [Upgrade Maxun With Docker Compose Setup](https://docs.maxun.dev/installation/upgrade#upgrading-with-docker-compose) +3. [Upgrade Maxun Without Docker Compose Setup](https://docs.maxun.dev/installation/upgrade#upgrading-with-local-setup) + +### How Does It Work? +Maxun lets you create custom robots which emulate user actions and extract data. A robot can perform any of the actions: Capture List, Capture Text or Capture Screenshot. Once a robot is created, it will keep extracting data for you without manual intervention. +1. Capture List: Useful to extract structured and bulk items from the website. 2. Capture Text: Useful to extract individual text content from the website. 3. Capture Screenshot: Get fullpage or visible section screenshots of the website. -# Features +### Sponsors + + + + +
+ +
+ CyberYozh App +
+
+ Infrastructure for developers working with multi‑accounting & automation in one place. +
+ +### Features - ✨ Extract Data With No-Code - ✨ Handle Pagination & Scrolling - ✨ Run Robots On A Specific Schedule @@ -134,11 +74,11 @@ Maxun lets you create custom robots which emulate user actions and extract data. - ✨ Integrations - ✨ MCP -# Use Cases +### Use Cases Maxun can be used for various use-cases, including lead generation, market research, content aggregation and more. View use-cases in detail here: https://www.maxun.dev/#usecases -# Screenshots +### Screenshots ![Maxun PH Launch (1)-1-1](https://github.com/user-attachments/assets/d7c75fa2-2bbc-47bb-a5f6-0ee6c162f391) ![Maxun PH Launch (1)-2-1](https://github.com/user-attachments/assets/d85a3ec7-8ce8-4daa-89aa-52d9617e227a) ![Maxun PH Launch (1)-3-1](https://github.com/user-attachments/assets/4bd5a0b4-485d-44f4-a487-edd9afc18b11) @@ -149,18 +89,18 @@ View use-cases in detail here: https://www.maxun.dev/#usecases ![Maxun PH Launch (1)-8-1](https://github.com/user-attachments/assets/16ee4a71-772a-49ae-a0e5-cb0529519bda) ![Maxun PH Launch (1)-9-1](https://github.com/user-attachments/assets/160f46fa-0357-4c1b-ba50-b4fe64453bb7) -# Note +### Note This project is in early stages of development. Your feedback is very important for us - we're actively working on improvements. -# License +### License

This project is licensed under AGPLv3.

-# Support Us +### Support Us Star the repository, contribute if you love what we’re building, or [sponsor us](https://github.com/sponsors/amhsirak). -# Contributors +### Contributors Thank you to the combined efforts of everyone who contributes! From 0bb2d9ec06e2400ec0a2de73ab06a7eef88295c3 Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 02:46:51 +0530 Subject: [PATCH 14/20] fix: adjust table cell width --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49fe260b0..f0959209c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Maxun lets you create custom robots which emulate user actions and extract data. ### Sponsors -
+
CyberYozh App From b39db8bc82f9f6df6cb9f6f32775d0ac0d7a0f3b Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 02:59:32 +0530 Subject: [PATCH 15/20] fix: width table cell --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f0959209c..0d0390051 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,9 @@ Maxun lets you create custom robots which emulate user actions and extract data. ### Sponsors -
+ -
+
CyberYozh App

From 1d2250275c135262a8687b627a1dc2f4a1242c12 Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 03:43:04 +0530 Subject: [PATCH 16/20] fix: remove unused img Removed an image from the README file. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 0d0390051..acff02e97 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,6 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web https://github.com/user-attachments/assets/c6baa75f-b950-482c-8d26-8a8b6c5382c3 - - ### Getting Started The simplest & fastest way to get started is to use the hosted version: https://app.maxun.dev. You can self-host if you like! From 15135e355b71ac999b06c604f21a8e10ce10b63c Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 16:05:46 +0530 Subject: [PATCH 17/20] chore: add LambdaTest to sponsors Added LambdaTest sponsorship information to README. --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index acff02e97..6fc328df8 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,18 @@ Maxun lets you create custom robots which emulate user actions and extract data. ### Sponsors +
+
+ +

+ LambdaTest +
+
+ GenAI-powered Quality Engineering Platform that empowers teams to test intelligently, smarter, and ship faster. +
-
+
CyberYozh App

From cde59e77eae64a7a4e1d69fa110f070d89f86ac9 Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 16:11:37 +0530 Subject: [PATCH 18/20] fix: table width --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6fc328df8..d2cceb9ac 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Maxun lets you create custom robots which emulate user actions and extract data. ### Sponsors - -
+


@@ -59,7 +59,7 @@ Maxun lets you create custom robots which emulate user actions and extract data.
GenAI-powered Quality Engineering Platform that empowers teams to test intelligently, smarter, and ship faster.
+
CyberYozh App From bbb0508444c51e7341b522e7340e31f4c68ec6fe Mon Sep 17 00:00:00 2001 From: Karishma Date: Thu, 30 Oct 2025 16:14:35 +0530 Subject: [PATCH 19/20] fix: td width --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2cceb9ac..f56e45640 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Maxun lets you create custom robots which emulate user actions and extract data. ### Sponsors -
+


From a94b2c5b9bf5ed8c9ca977ae1d065b7559508850 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 1 Nov 2025 19:07:13 +0530 Subject: [PATCH 20/20] feat: // Get the corresponding scrapeList action to extract its name --- src/components/robot/pages/RobotEditPage.tsx | 51 ++++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index 96f7c9d3f..30f5f2e19 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -510,27 +510,36 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { {t("List Limits")} - {scrapeListLimits.map((limitInfo, index) => ( - { - const value = parseInt(e.target.value, 10); - if (value >= 1) { - handleLimitChange( - limitInfo.pairIndex, - limitInfo.actionIndex, - limitInfo.argIndex, - value - ); - } - }} - inputProps={{ min: 1 }} - style={{ marginBottom: "20px" }} - /> - ))} + {scrapeListLimits.map((limitInfo, index) => { + // Get the corresponding scrapeList action to extract its name + const scrapeListAction = robot?.recording?.workflow?.[limitInfo.pairIndex]?.what?.[limitInfo.actionIndex]; + const actionName = + scrapeListAction?.name || + (scrapeListAction?.args?.[0]?.__name) || + `List Limit ${index + 1}`; + + return ( + { + const value = parseInt(e.target.value, 10); + if (value >= 1) { + handleLimitChange( + limitInfo.pairIndex, + limitInfo.actionIndex, + limitInfo.argIndex, + value + ); + } + }} + inputProps={{ min: 1 }} + style={{ marginBottom: "20px" }} + /> + ); + })} ); };