Browse Source

feat: add testing mode

Ahmad Kholid 2 years ago
parent
commit
c5a6e6e5e8

+ 8 - 0
src/background/index.js

@@ -191,6 +191,14 @@ message.on('recording:stop', async () => {
     console.error(error);
     console.error(error);
   }
   }
 });
 });
+message.on('workflow:resume', ({ id, nextBlock }) => {
+  if (!id) return;
+  workflowState.resume(id, nextBlock);
+});
+message.on('workflow:breakpoint', (id) => {
+  if (!id) return;
+  workflowState.update(id, { status: 'breakpoint' });
+});
 
 
 automa('background', message);
 automa('background', message);
 
 

+ 10 - 0
src/components/block/BlockBase.vue

@@ -65,6 +65,15 @@
     </div>
     </div>
     <slot name="prepend" />
     <slot name="prepend" />
     <ui-card :class="contentClass" class="block-base__content relative z-10">
     <ui-card :class="contentClass" class="block-base__content relative z-10">
+      <v-remixicon
+        v-if="workflow?.data?.value.testingMode"
+        :class="{ 'text-red-500 dark:text-red-400': data.$breakpoint }"
+        class="absolute left-0 top-0"
+        name="riRecordCircleFill"
+        title="Set as breakpoint"
+        size="20"
+        @click="$emit('update', { $breakpoint: !data.$breakpoint })"
+      />
       <slot></slot>
       <slot></slot>
     </ui-card>
     </ui-card>
     <slot name="append" />
     <slot name="append" />
@@ -95,6 +104,7 @@ const props = defineProps({
 defineEmits(['delete', 'edit', 'update', 'settings']);
 defineEmits(['delete', 'edit', 'update', 'settings']);
 
 
 const isCopied = ref(false);
 const isCopied = ref(false);
+const workflow = inject('workflow', null);
 const workflowUtils = inject('workflow-utils', null);
 const workflowUtils = inject('workflow-utils', null);
 
 
 function insertToClipboard() {
 function insertToClipboard() {

+ 169 - 0
src/components/newtab/workflow/editor/EditorDebugging.vue

@@ -0,0 +1,169 @@
+<template>
+  <ui-card
+    v-if="workflowState?.state"
+    class="shadow-xl flex items-start fixed bottom-8 z-50 left-1/2 -translate-x-1/2"
+  >
+    <div class="mr-4 w-52">
+      <div class="flex items-center gap-2">
+        <ui-button
+          :disabled="workflowState.state.nextBlockBreakpoint"
+          variant="accent"
+          class="flex-1"
+          @click="toggleExecution"
+        >
+          <v-remixicon
+            :name="
+              workflowState.status === 'breakpoint'
+                ? 'riPlayLine'
+                : 'riPauseLine'
+            "
+            class="mr-2 -ml-1"
+          />
+          <span>
+            {{
+              t(
+                `common.${
+                  workflowState.status === 'breakpoint' ? 'resume' : 'pause'
+                }`
+              )
+            }}
+          </span>
+        </ui-button>
+        <ui-button
+          v-tooltip="t('workflow.testing.nextBlock')"
+          :disabled="workflowState.status !== 'breakpoint'"
+          icon
+          @click="nextBlock"
+        >
+          <v-remixicon name="riArrowLeftSLine" rotate="180" />
+        </ui-button>
+        <ui-button
+          v-tooltip="t('common.stop')"
+          icon
+          class="text-red-500 dark:text-red-600"
+          @click="stopWorkflow"
+        >
+          <v-remixicon name="riStopLine" />
+        </ui-button>
+      </div>
+      <ui-list
+        v-if="workflowState.state"
+        class="mt-4 overflow-auto h-[105px] scroll"
+      >
+        <ui-list-item
+          v-for="block in workflowState.state.currentBlock"
+          :key="block.id"
+          small
+        >
+          <div class="text-overflow text-sm w-full">
+            <div class="flex items-center">
+              <p class="flex-1 text-overflow">
+                {{ getBlockName(block.name) }}
+              </p>
+              <v-remixicon
+                title="Go to block"
+                name="riEyeLine"
+                size="18"
+                class="text-gray-600 dark:text-gray-200 cursor-pointer"
+                @click="$emit('goToBlock', block.id)"
+              />
+            </div>
+            <p
+              class="leading-tight text-overflow text-gray-600 dark:text-gray-200"
+            >
+              {{ t('workflow.testing.startRun') }}:
+              {{ dayjs(block.startedAt).format('HH:mm:ss, SSS') }}
+            </p>
+          </div>
+        </ui-list-item>
+      </ui-list>
+    </div>
+    <shared-codemirror
+      :model-value="JSON.stringify(workflowData, null, 2)"
+      :line-numbers="false"
+      hide-lang
+      readonly
+      lang="json"
+      class="h-40 w-64 scroll breakpoint-data"
+    />
+  </ui-card>
+</template>
+<script setup>
+import { defineAsyncComponent, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import dayjs from '@/lib/dayjs';
+import { tasks } from '@/utils/shared';
+import { sendMessage } from '@/utils/message';
+
+const SharedCodemirror = defineAsyncComponent(() =>
+  import('@/components/newtab/shared/SharedCodemirror.vue')
+);
+
+const props = defineProps({
+  states: {
+    type: Array,
+    default: () => [],
+  },
+  editor: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+defineEmits(['goToBlock']);
+
+const { t, te } = useI18n();
+
+const workflowState = computed(() => props.states[0]);
+const workflowData = computed(() => {
+  if (!workflowState.value?.state?.ctxData) return {};
+  const { ctxData, dataSnapshot } = workflowState.value.state.ctxData;
+  const latestData = Object.values(ctxData).at(-1);
+  if (!latestData) return {};
+
+  return {
+    ...latestData,
+    referenceData: {
+      ...latestData.referenceData,
+      loopData: dataSnapshot[latestData.referenceData.loopData] ?? {},
+      variables: dataSnapshot[latestData.referenceData.variables] ?? {},
+    },
+  };
+});
+
+function getBlockName(blockId) {
+  const key = `workflow.blocks.${blockId}.name`;
+
+  return te(key) ? t(key) : tasks[blockId].name;
+}
+function toggleExecution() {
+  if (!workflowState.value) return;
+
+  if (workflowState.value.status === 'running') {
+    sendMessage('workflow:breakpoint', workflowState.value.id, 'background');
+  } else {
+    sendMessage(
+      'workflow:resume',
+      { id: workflowState.value.id },
+      'background'
+    );
+  }
+}
+function stopWorkflow() {
+  if (!workflowState.value) return;
+
+  sendMessage('workflow:stop', workflowState.value.id, 'background');
+}
+function nextBlock() {
+  sendMessage(
+    'workflow:resume',
+    { id: workflowState.value.id, nextBlock: true },
+    'background'
+  );
+}
+</script>
+<style>
+.breakpoint-data .cm-editor {
+  font-size: 13px;
+  padding-bottom: 0;
+}
+</style>

+ 45 - 23
src/components/newtab/workflow/editor/EditorLocalActions.vue

@@ -122,18 +122,35 @@
         </ui-list-item>
         </ui-list-item>
       </ui-list>
       </ui-list>
     </ui-popover>
     </ui-popover>
-    <button
-      v-if="!workflow.isDisabled"
-      v-tooltip.group="
-        `${t('common.execute')} (${
-          shortcuts['editor:execute-workflow'].readable
-        })`
-      "
-      class="hoverable rounded-lg p-2"
-      @click="executeCurrWorkflow"
-    >
-      <v-remixicon name="riPlayLine" />
-    </button>
+    <template v-if="!workflow.isDisabled">
+      <button
+        v-if="canEdit"
+        v-tooltip.group="
+          t(`workflow.testing.${isDataChanged ? 'disabled' : 'title'}`)
+        "
+        :class="[
+          { 'cursor-default': isDataChanged },
+          workflow.testingMode
+            ? 'bg-primary bg-primary bg-opacity-20 text-primary'
+            : 'hoverable',
+        ]"
+        class="rounded-lg p-2"
+        @click="toggleTestingMode"
+      >
+        <v-remixicon name="riBug2Line" />
+      </button>
+      <button
+        v-tooltip.group="
+          `${t('common.execute')} (${
+            shortcuts['editor:execute-workflow'].readable
+          })`
+        "
+        class="hoverable rounded-lg p-2"
+        @click="executeCurrWorkflow"
+      >
+        <v-remixicon name="riPlayLine" />
+      </button>
+    </template>
     <button
     <button
       v-else
       v-else
       v-tooltip="t('workflow.clickToEnable')"
       v-tooltip="t('workflow.clickToEnable')"
@@ -401,17 +418,6 @@ const userDontHaveTeamsAccess = computed(() => {
   );
   );
 });
 });
 
 
-function copyWorkflowId() {
-  navigator.clipboard.writeText(props.workflow.id).catch((error) => {
-    console.error(error);
-
-    const textarea = document.createElement('textarea');
-    textarea.value = props.workflow.id;
-    textarea.select();
-    document.execCommand('copy');
-    textarea.blur();
-  });
-}
 function updateWorkflow(data = {}, changedIndicator = false) {
 function updateWorkflow(data = {}, changedIndicator = false) {
   let store = null;
   let store = null;
 
 
@@ -434,6 +440,22 @@ function updateWorkflow(data = {}, changedIndicator = false) {
     return result;
     return result;
   });
   });
 }
 }
+function toggleTestingMode() {
+  if (props.isDataChanged) return;
+
+  updateWorkflow({ testingMode: !props.workflow.testingMode });
+}
+function copyWorkflowId() {
+  navigator.clipboard.writeText(props.workflow.id).catch((error) => {
+    console.error(error);
+
+    const textarea = document.createElement('textarea');
+    textarea.value = props.workflow.id;
+    textarea.select();
+    document.execCommand('copy');
+    textarea.blur();
+  });
+}
 function updateWorkflowDescription(value) {
 function updateWorkflowDescription(value) {
   const keys = ['description', 'category', 'content', 'tag', 'name'];
   const keys = ['description', 'category', 'content', 'tag', 'name'];
   const payload = {};
   const payload = {};

+ 4 - 0
src/lib/vRemixicon.js

@@ -22,6 +22,7 @@ import {
   riTimeLine,
   riTimeLine,
   riFlagLine,
   riFlagLine,
   riFileLine,
   riFileLine,
+  riBug2Line,
   riTeamLine,
   riTeamLine,
   riLinksLine,
   riLinksLine,
   riGroupLine,
   riGroupLine,
@@ -122,6 +123,7 @@ import {
   riArrowGoBackLine,
   riArrowGoBackLine,
   riInputCursorMove,
   riInputCursorMove,
   riCloseCircleLine,
   riCloseCircleLine,
+  riRecordCircleFill,
   riRecordCircleLine,
   riRecordCircleLine,
   riErrorWarningLine,
   riErrorWarningLine,
   riExternalLinkLine,
   riExternalLinkLine,
@@ -160,6 +162,7 @@ export const icons = {
   riTimeLine,
   riTimeLine,
   riFlagLine,
   riFlagLine,
   riFileLine,
   riFileLine,
+  riBug2Line,
   riTeamLine,
   riTeamLine,
   riLinksLine,
   riLinksLine,
   riGroupLine,
   riGroupLine,
@@ -260,6 +263,7 @@ export const icons = {
   riArrowGoBackLine,
   riArrowGoBackLine,
   riInputCursorMove,
   riInputCursorMove,
   riCloseCircleLine,
   riCloseCircleLine,
+  riRecordCircleFill,
   riRecordCircleLine,
   riRecordCircleLine,
   riErrorWarningLine,
   riErrorWarningLine,
   riExternalLinkLine,
   riExternalLinkLine,

+ 2 - 0
src/locales/en/common.json

@@ -27,6 +27,8 @@
     "data": "data",
     "data": "data",
     "stop": "Stop",
     "stop": "Stop",
     "sheet": "Sheet",
     "sheet": "Sheet",
+    "pause": "Pause",
+    "resume": "Resume",
     "action": "Action | Actions",
     "action": "Action | Actions",
     "packages": "Packages",
     "packages": "Packages",
     "storage": "Storage",
     "storage": "Storage",

+ 6 - 0
src/locales/en/newtab.json

@@ -196,6 +196,12 @@
       "preferInTab": "Prefer input parameters in the tab"
       "preferInTab": "Prefer input parameters in the tab"
     },
     },
     "my": "My workflows",
     "my": "My workflows",
+    "testing": {
+      "title": "Testing mode",
+      "nextBlock": "Next block",
+      "startRun": "Start run at",
+      "disabled": "Save changes first"
+    },
     "import": "Import workflow",
     "import": "Import workflow",
     "new": "New workflow",
     "new": "New workflow",
     "delete": "Delete workflow",
     "delete": "Delete workflow",

+ 0 - 1
src/newtab/App.vue

@@ -264,7 +264,6 @@ browser.runtime.onMessage.addListener(({ type, data }) => {
 
 
 browser.storage.local.onChanged.addListener(({ workflowStates }) => {
 browser.storage.local.onChanged.addListener(({ workflowStates }) => {
   if (!workflowStates) return;
   if (!workflowStates) return;
-
   const states = Object.values(workflowStates.newValue);
   const states = Object.values(workflowStates.newValue);
   workflowStore.states = states;
   workflowStore.states = states;
 });
 });

+ 6 - 0
src/newtab/pages/workflows/[id].vue

@@ -156,6 +156,11 @@
           </ui-tab-panel>
           </ui-tab-panel>
         </template>
         </template>
         <ui-tab-panel cache value="editor" class="w-full" @keydown="onKeydown">
         <ui-tab-panel cache value="editor" class="w-full" @keydown="onKeydown">
+          <editor-debugging
+            v-if="workflow.testingMode && workflowStates.length > 0"
+            :states="workflowStates"
+            @goToBlock="goToBlock"
+          />
           <workflow-editor
           <workflow-editor
             v-if="state.workflowConverted"
             v-if="state.workflowConverted"
             :id="route.params.id"
             :id="route.params.id"
@@ -337,6 +342,7 @@ import WorkflowGlobalData from '@/components/newtab/workflow/WorkflowGlobalData.
 import WorkflowDetailsCard from '@/components/newtab/workflow/WorkflowDetailsCard.vue';
 import WorkflowDetailsCard from '@/components/newtab/workflow/WorkflowDetailsCard.vue';
 import SharedPermissionsModal from '@/components/newtab/shared/SharedPermissionsModal.vue';
 import SharedPermissionsModal from '@/components/newtab/shared/SharedPermissionsModal.vue';
 import EditorAddPackage from '@/components/newtab/workflow/editor/EditorAddPackage.vue';
 import EditorAddPackage from '@/components/newtab/workflow/editor/EditorAddPackage.vue';
+import EditorDebugging from '@/components/newtab/workflow/editor/EditorDebugging.vue';
 import EditorPkgActions from '@/components/newtab/workflow/editor/EditorPkgActions.vue';
 import EditorPkgActions from '@/components/newtab/workflow/editor/EditorPkgActions.vue';
 import EditorLocalCtxMenu from '@/components/newtab/workflow/editor/EditorLocalCtxMenu.vue';
 import EditorLocalCtxMenu from '@/components/newtab/workflow/editor/EditorLocalCtxMenu.vue';
 import EditorLocalActions from '@/components/newtab/workflow/editor/EditorLocalActions.vue';
 import EditorLocalActions from '@/components/newtab/workflow/editor/EditorLocalActions.vue';

+ 11 - 0
src/workflowEngine/WorkflowEngine.js

@@ -16,6 +16,7 @@ class WorkflowEngine {
     this.workflow = workflow;
     this.workflow = workflow;
     this.isPopup = isPopup ?? true;
     this.isPopup = isPopup ?? true;
     this.blocksHandler = blocksHandler;
     this.blocksHandler = blocksHandler;
+    this.isTestingMode = workflow.testingMode;
     this.parentWorkflow = options?.parentWorkflow;
     this.parentWorkflow = options?.parentWorkflow;
     this.saveLog = workflow.settings?.saveLog ?? true;
     this.saveLog = workflow.settings?.saveLog ?? true;
     this.isMV2 = browser.runtime.getManifest().manifest_version === 2;
     this.isMV2 = browser.runtime.getManifest().manifest_version === 2;
@@ -30,6 +31,7 @@ class WorkflowEngine {
 
 
     this.isDestroyed = false;
     this.isDestroyed = false;
     this.isUsingProxy = false;
     this.isUsingProxy = false;
+    this.isInBreakpoint = false;
 
 
     this.triggerBlockId = null;
     this.triggerBlockId = null;
 
 
@@ -103,6 +105,13 @@ class WorkflowEngine {
       if (this.id !== id || this.isDestroyed) return;
       if (this.id !== id || this.isDestroyed) return;
       this.stop();
       this.stop();
     };
     };
+    this.onResumeExecution = ({ id, nextBlock }) => {
+      if (this.id !== id || this.isDestroyed) return;
+
+      this.workers.forEach((worker) => {
+        worker.resume(nextBlock);
+      });
+    };
   }
   }
 
 
   async init() {
   async init() {
@@ -269,6 +278,7 @@ class WorkflowEngine {
       this.startedTimestamp = Date.now();
       this.startedTimestamp = Date.now();
 
 
       this.states.on('stop', this.onWorkflowStopped);
       this.states.on('stop', this.onWorkflowStopped);
+      this.states.on('resume', this.onResumeExecution);
 
 
       const credentials = await dbStorage.credentials.toArray();
       const credentials = await dbStorage.credentials.toArray();
       credentials.forEach(({ name, value }) => {
       credentials.forEach(({ name, value }) => {
@@ -284,6 +294,7 @@ class WorkflowEngine {
 
 
       await this.states.add(this.id, {
       await this.states.add(this.id, {
         id: this.id,
         id: this.id,
+        status: 'running',
         state: this.state,
         state: this.state,
         workflowId: this.workflow.id,
         workflowId: this.workflow.id,
         parentState: this.parentWorkflow,
         parentState: this.parentWorkflow,

+ 20 - 0
src/workflowEngine/WorkflowState.js

@@ -81,8 +81,28 @@ class WorkflowState {
     return id;
     return id;
   }
   }
 
 
+  async resume(id, nextBlock) {
+    const state = this.states.get(id);
+    if (!state) return;
+
+    this.states.set(id, {
+      ...state,
+      status: 'running',
+    });
+    await this._saveToStorage();
+
+    this.dispatchEvent('resume', { id, nextBlock });
+  }
+
   async update(id, data = {}) {
   async update(id, data = {}) {
     const state = this.states.get(id);
     const state = this.states.get(id);
+    if (!state) return;
+
+    if (data?.state?.status) {
+      state.status = data.state.status;
+      delete data.state.status;
+    }
+
     this.states.set(id, { ...state, ...data });
     this.states.set(id, { ...state, ...data });
     this.dispatchEvent('update', { id, data });
     this.dispatchEvent('update', { id, data });
     await this._saveToStorage();
     await this._saveToStorage();

+ 59 - 12
src/workflowEngine/WorkflowWorker.js

@@ -46,6 +46,7 @@ class WorkflowWorker {
     this.loopList = {};
     this.loopList = {};
     this.repeatedTasks = {};
     this.repeatedTasks = {};
     this.preloadScripts = [];
     this.preloadScripts = [];
+    this.breakpointState = null;
 
 
     this.windowId = null;
     this.windowId = null;
     this.currentBlock = null;
     this.currentBlock = null;
@@ -135,13 +136,22 @@ class WorkflowWorker {
     return [...connections.values()];
     return [...connections.values()];
   }
   }
 
 
-  executeNextBlocks(connections, prevBlockData) {
+  executeNextBlocks(
+    connections,
+    prevBlockData,
+    nextBlockBreakpointCount = null
+  ) {
     connections.forEach((connection, index) => {
     connections.forEach((connection, index) => {
       const { id, targetHandle, sourceHandle } =
       const { id, targetHandle, sourceHandle } =
         typeof connection === 'string'
         typeof connection === 'string'
           ? { id: connection, targetHandle: '', sourceHandle: '' }
           ? { id: connection, targetHandle: '', sourceHandle: '' }
           : connection;
           : connection;
-      const execParam = { prevBlockData, targetHandle, sourceHandle };
+      const execParam = {
+        prevBlockData,
+        targetHandle,
+        sourceHandle,
+        nextBlockBreakpointCount,
+      };
 
 
       if (index === 0) {
       if (index === 0) {
         this.executeBlock(this.engine.blocks[id], {
         this.executeBlock(this.engine.blocks[id], {
@@ -167,6 +177,19 @@ class WorkflowWorker {
     });
     });
   }
   }
 
 
+  resume(nextBlock) {
+    if (!this.breakpointState) return;
+
+    const { block, execParam, isRetry } = this.breakpointState;
+    const payload = { ...execParam, resume: true };
+
+    payload.nextBlockBreakpointCount = nextBlock ? 1 : null;
+
+    this.executeBlock(block, payload, isRetry);
+
+    this.breakpointState = null;
+  }
+
   async executeBlock(block, execParam = {}, isRetry = false) {
   async executeBlock(block, execParam = {}, isRetry = false) {
     const currentState = await this.engine.states.get(this.engine.id);
     const currentState = await this.engine.states.get(this.engine.id);
 
 
@@ -181,11 +204,32 @@ class WorkflowWorker {
     const prevBlock = this.currentBlock;
     const prevBlock = this.currentBlock;
     this.currentBlock = { ...block, startedAt: startExecuteTime };
     this.currentBlock = { ...block, startedAt: startExecuteTime };
 
 
+    const isInBreakpoint =
+      this.engine.isTestingMode &&
+      ((block.data?.$breakpoint && !execParam.resume) ||
+        execParam.nextBlockBreakpointCount === 0);
+
     if (!isRetry) {
     if (!isRetry) {
-      await this.engine.updateState({
+      const payload = {
         activeTabUrl: this.activeTab.url,
         activeTabUrl: this.activeTab.url,
         childWorkflowId: this.childWorkflowId,
         childWorkflowId: this.childWorkflowId,
-      });
+        nextBlockBreakpoint: Boolean(execParam.nextBlockBreakpointCount),
+      };
+      if (isInBreakpoint && currentState.status !== 'breakpoint')
+        payload.status = 'breakpoint';
+
+      await this.engine.updateState(payload);
+    }
+
+    if (execParam.nextBlockBreakpointCount) {
+      execParam.nextBlockBreakpointCount -= 1;
+    }
+
+    if (isInBreakpoint || currentState.status === 'breakpoint') {
+      this.engine.isInBreakpoint = true;
+      this.breakpointState = { block, execParam, isRetry };
+
+      return;
     }
     }
 
 
     const blockHandler = this.engine.blocksHandler[toCamelCase(block.label)];
     const blockHandler = this.engine.blocksHandler[toCamelCase(block.label)];
@@ -238,6 +282,14 @@ class WorkflowWorker {
       });
       });
     };
     };
 
 
+    const executeBlocks = (blocks, data) => {
+      return this.executeNextBlocks(
+        blocks,
+        data,
+        execParam.nextBlockBreakpointCount
+      );
+    };
+
     try {
     try {
       let result;
       let result;
 
 
@@ -253,11 +305,6 @@ class WorkflowWorker {
           ...(execParam || {}),
           ...(execParam || {}),
         });
         });
         result = await blockExecutionWrapper(bindedHandler, block.data);
         result = await blockExecutionWrapper(bindedHandler, block.data);
-        // result = await handler.call(this, replacedBlock, {
-        //   refData,
-        //   prevBlock,
-        //   ...(execParam || {}),
-        // });
 
 
         if (this.engine.isDestroyed) return;
         if (this.engine.isDestroyed) return;
 
 
@@ -273,7 +320,7 @@ class WorkflowWorker {
 
 
       if (result.nextBlockId && !result.destroyWorker) {
       if (result.nextBlockId && !result.destroyWorker) {
         setTimeout(() => {
         setTimeout(() => {
-          this.executeNextBlocks(result.nextBlockId, result.data);
+          executeBlocks(result.nextBlockId, result.data);
         }, blockDelay);
         }, blockDelay);
       } else {
       } else {
         this.engine.destroyWorker(this.id);
         this.engine.destroyWorker(this.id);
@@ -319,7 +366,7 @@ class WorkflowWorker {
         if (blockOnError.toDo !== 'error' && nextBlocks) {
         if (blockOnError.toDo !== 'error' && nextBlocks) {
           addBlockLog('error', errorLogData);
           addBlockLog('error', errorLogData);
 
 
-          this.executeNextBlocks(nextBlocks, prevBlockData);
+          executeBlocks(nextBlocks, prevBlockData);
 
 
           return;
           return;
         }
         }
@@ -335,7 +382,7 @@ class WorkflowWorker {
 
 
       if (onError === 'keep-running' && nodeConnections) {
       if (onError === 'keep-running' && nodeConnections) {
         setTimeout(() => {
         setTimeout(() => {
-          this.executeNextBlocks(nodeConnections, error.data || '');
+          executeBlocks(nodeConnections, error.data || '');
         }, blockDelay);
         }, blockDelay);
       } else if (onError === 'restart-workflow' && !this.parentWorkflow) {
       } else if (onError === 'restart-workflow' && !this.parentWorkflow) {
         const restartCount = this.engine.restartWorkersCount[this.id] || 0;
         const restartCount = this.engine.restartWorkersCount[this.id] || 0;

+ 2 - 0
src/workflowEngine/blocksHandler/handlerExecuteWorkflow.js

@@ -72,6 +72,8 @@ async function executeWorkflow({ id: blockId, data }) {
   workflow = convertWorkflowData(workflow);
   workflow = convertWorkflowData(workflow);
   const optionsParams = { variables: {} };
   const optionsParams = { variables: {} };
 
 
+  if (workflow.testingMode) workflow.testingMode = false;
+
   if (!isWhitespace(data.globalData))
   if (!isWhitespace(data.globalData))
     optionsParams.globalData = data.globalData;
     optionsParams.globalData = data.globalData;
 
 

+ 3 - 8
src/workflowEngine/index.js

@@ -2,7 +2,6 @@
 import { toRaw } from 'vue';
 import { toRaw } from 'vue';
 import browser from 'webextension-polyfill';
 import browser from 'webextension-polyfill';
 import dayjs from '@/lib/dayjs';
 import dayjs from '@/lib/dayjs';
-import decryptFlow, { getWorkflowPass } from '@/utils/decryptFlow';
 import { parseJSON } from '@/utils/helper';
 import { parseJSON } from '@/utils/helper';
 import { fetchApi } from '@/utils/api';
 import { fetchApi } from '@/utils/api';
 import { sendMessage } from '@/utils/message';
 import { sendMessage } from '@/utils/message';
@@ -45,13 +44,9 @@ export function startWorkflowExec(workflowData, options, isPopup = true) {
     self.localStorage.setItem('runCounts', JSON.stringify(runCounts));
     self.localStorage.setItem('runCounts', JSON.stringify(runCounts));
   }
   }
 
 
-  if (workflowData.isProtected) {
-    const flow = parseJSON(workflowData.drawflow, null);
-
-    if (!flow) {
-      const pass = getWorkflowPass(workflowData.pass);
-
-      workflowData.drawflow = decryptFlow(workflowData, pass);
+  if (workflowData.testingMode) {
+    for (const value of workflowState.states.values()) {
+      if (value.workflowId === workflowData.id) return null;
     }
     }
   }
   }