feat(FN-677): add browser verification workflow step template
- Add browser verification workflow step template to WORKFLOW_STEP_TEMPLATES - Update NewTaskModal UI with browser verification checkbox - Update InlineCreateCard UI with browser verification toggle - Add tests for browser verification feature - Create changeset for minor version bump
This commit is contained in:
5
.changeset/browser-verification-workflow.md
Normal file
5
.changeset/browser-verification-workflow.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@gsxdsm/fusion": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add Browser Verification workflow step template for agent-browser-based web app verification
|
||||||
@@ -229,6 +229,42 @@ Output Requirements:
|
|||||||
- If accessibility requirements are met: call task_done() with success status
|
- If accessibility requirements are met: call task_done() with success status
|
||||||
- If issues found: describe each issue with specific file paths, WCAG guideline references, and remediation steps via task_log()`,
|
- If issues found: describe each issue with specific file paths, WCAG guideline references, and remediation steps via task_log()`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "browser-verification",
|
||||||
|
name: "Browser Verification",
|
||||||
|
description: "Verify web application functionality using browser automation",
|
||||||
|
category: "Quality",
|
||||||
|
icon: "globe",
|
||||||
|
prompt: `You are a browser verification specialist. Verify web application functionality after task implementation using the agent-browser CLI tool.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
First, determine the URL to verify. Check the task PROMPT.md for any URLs mentioned, or look at the code changes to identify the local development server URL (typically http://localhost:3000, http://localhost:5173, http://localhost:8080, etc.).
|
||||||
|
|
||||||
|
## Verification Commands
|
||||||
|
Use these agent-browser commands for verification:
|
||||||
|
- \`agent-browser open <url>\` — Navigate to the page
|
||||||
|
- \`agent-browser snapshot -i\` — Get interactive elements with refs (@e1, @e2, etc.)
|
||||||
|
- \`agent-browser click @e1\` — Click an element
|
||||||
|
- \`agent-browser fill @e1 "text"\` — Fill an input field
|
||||||
|
- \`agent-browser get text @e1\` — Get element text content
|
||||||
|
- \`agent-browser screenshot\` — Capture screenshot to file
|
||||||
|
- \`agent-browser wait --load networkidle\` — Wait for page to fully load
|
||||||
|
|
||||||
|
## Verification Checklist
|
||||||
|
1. Page loads without JavaScript errors or blank screens
|
||||||
|
2. Navigation between pages/sections works
|
||||||
|
3. Forms accept input and submit correctly
|
||||||
|
4. Interactive elements (buttons, links) respond to clicks
|
||||||
|
5. Error states are handled gracefully
|
||||||
|
6. Screenshots capture expected content
|
||||||
|
|
||||||
|
## Output Requirements
|
||||||
|
- If verification succeeds: call task_done() with success status
|
||||||
|
- If verification fails: describe what failed and how it should behave via task_log()
|
||||||
|
- Include screenshots as evidence of verification results
|
||||||
|
|
||||||
|
Note: Refs (@e1, @e2) are invalidated after page navigation. Re-snapshot after clicking links or form submissions.`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface PrInfo {
|
export interface PrInfo {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useCallback, useEffect, useRef } from "react";
|
import { useState, useCallback, useEffect, useRef } from "react";
|
||||||
import { Brain, Link, Lightbulb, ListTree, Zap, ChevronDown, ChevronUp } from "lucide-react";
|
import { Brain, Link, Lightbulb, ListTree, Zap, ChevronDown, ChevronUp, Globe } from "lucide-react";
|
||||||
import type { Task, TaskCreateInput, Settings } from "@fusion/core";
|
import type { Task, TaskCreateInput, Settings } from "@fusion/core";
|
||||||
import type { ToastType } from "../hooks/useToast";
|
import type { ToastType } from "../hooks/useToast";
|
||||||
import { fetchModels, uploadAttachment, fetchSettings, updateGlobalSettings } from "../api";
|
import { fetchModels, uploadAttachment, fetchSettings, updateGlobalSettings } from "../api";
|
||||||
@@ -89,6 +89,7 @@ export function InlineCreateCard({
|
|||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [pendingImages, setPendingImages] = useState<PendingImage[]>([]);
|
const [pendingImages, setPendingImages] = useState<PendingImage[]>([]);
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const [browserVerification, setBrowserVerification] = useState(false);
|
||||||
const justResetRef = useRef(false);
|
const justResetRef = useRef(false);
|
||||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const cardRef = useRef<HTMLDivElement>(null);
|
const cardRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -284,6 +285,7 @@ export function InlineCreateCard({
|
|||||||
modelId: hasExecutorOverride ? executorModelId : undefined,
|
modelId: hasExecutorOverride ? executorModelId : undefined,
|
||||||
validatorModelProvider: hasValidatorOverride ? validatorProvider : undefined,
|
validatorModelProvider: hasValidatorOverride ? validatorProvider : undefined,
|
||||||
validatorModelId: hasValidatorOverride ? validatorModelId : undefined,
|
validatorModelId: hasValidatorOverride ? validatorModelId : undefined,
|
||||||
|
enabledWorkflowSteps: browserVerification ? ["browser-verification"] : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Upload pending images as attachments
|
// Upload pending images as attachments
|
||||||
@@ -318,6 +320,7 @@ export function InlineCreateCard({
|
|||||||
setShowDeps(false);
|
setShowDeps(false);
|
||||||
setShowModels(false);
|
setShowModels(false);
|
||||||
setShowPresets(false);
|
setShowPresets(false);
|
||||||
|
setBrowserVerification(false);
|
||||||
addToast(`Created ${task.id}`, "success");
|
addToast(`Created ${task.id}`, "success");
|
||||||
|
|
||||||
// Collapse and clear localStorage after successful task creation
|
// Collapse and clear localStorage after successful task creation
|
||||||
@@ -345,6 +348,7 @@ export function InlineCreateCard({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
addToast,
|
addToast,
|
||||||
selectedPresetId,
|
selectedPresetId,
|
||||||
|
browserVerification,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
@@ -463,6 +467,7 @@ export function InlineCreateCard({
|
|||||||
setShowDeps(false);
|
setShowDeps(false);
|
||||||
setShowModels(false);
|
setShowModels(false);
|
||||||
setShowPresets(false);
|
setShowPresets(false);
|
||||||
|
setBrowserVerification(false);
|
||||||
setIsExpanded(false);
|
setIsExpanded(false);
|
||||||
}, [description, onPlanningMode, addToast]);
|
}, [description, onPlanningMode, addToast]);
|
||||||
|
|
||||||
@@ -484,6 +489,7 @@ export function InlineCreateCard({
|
|||||||
setShowDeps(false);
|
setShowDeps(false);
|
||||||
setShowModels(false);
|
setShowModels(false);
|
||||||
setShowPresets(false);
|
setShowPresets(false);
|
||||||
|
setBrowserVerification(false);
|
||||||
setIsExpanded(false);
|
setIsExpanded(false);
|
||||||
}, [description, onSubtaskBreakdown, addToast]);
|
}, [description, onSubtaskBreakdown, addToast]);
|
||||||
|
|
||||||
@@ -775,6 +781,17 @@ export function InlineCreateCard({
|
|||||||
<ListTree size={12} style={{ verticalAlign: "middle" }} />
|
<ListTree size={12} style={{ verticalAlign: "middle" }} />
|
||||||
Subtask
|
Subtask
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm ${browserVerification ? "btn-active" : ""}`}
|
||||||
|
onClick={() => setBrowserVerification((v) => !v)}
|
||||||
|
disabled={submitting}
|
||||||
|
data-testid="browser-verification-toggle"
|
||||||
|
title="Verify with agent-browser"
|
||||||
|
>
|
||||||
|
<Globe size={12} style={{ verticalAlign: "middle" }} />
|
||||||
|
Browser
|
||||||
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { uploadAttachment, fetchModels, fetchSettings, fetchWorkflowSteps, refin
|
|||||||
import type { ModelInfo } from "../api";
|
import type { ModelInfo } from "../api";
|
||||||
import { applyPresetToSelection, getRecommendedPresetForSize } from "../utils/modelPresets";
|
import { applyPresetToSelection, getRecommendedPresetForSize } from "../utils/modelPresets";
|
||||||
import { CustomModelDropdown } from "./CustomModelDropdown";
|
import { CustomModelDropdown } from "./CustomModelDropdown";
|
||||||
import { Sparkles } from "lucide-react";
|
import { Sparkles, Globe } from "lucide-react";
|
||||||
|
|
||||||
const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
||||||
|
|
||||||
@@ -585,44 +585,71 @@ export function NewTaskModal({ isOpen, onClose, tasks, onCreateTask, addToast, o
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Workflow Steps */}
|
{/* Workflow Steps */}
|
||||||
{workflowSteps.length > 0 && (
|
<div className="form-group" data-testid="workflow-steps-section">
|
||||||
<div className="form-group" data-testid="workflow-steps-section">
|
<label>Workflow Steps</label>
|
||||||
<label>Workflow Steps</label>
|
<small style={{ marginBottom: "8px", display: "block" }}>
|
||||||
<small style={{ marginBottom: "8px", display: "block" }}>
|
Select steps to run after task implementation completes
|
||||||
Select steps to run after task implementation completes
|
</small>
|
||||||
</small>
|
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
{workflowSteps.length > 0 && workflowSteps.map((step) => (
|
||||||
{workflowSteps.map((step) => (
|
<label
|
||||||
<label
|
key={step.id}
|
||||||
key={step.id}
|
className="checkbox-label"
|
||||||
className="checkbox-label"
|
style={{ display: "flex", alignItems: "flex-start", gap: "8px" }}
|
||||||
style={{ display: "flex", alignItems: "flex-start", gap: "8px" }}
|
data-testid={`workflow-step-checkbox-${step.id}`}
|
||||||
data-testid={`workflow-step-checkbox-${step.id}`}
|
>
|
||||||
>
|
<input
|
||||||
<input
|
type="checkbox"
|
||||||
type="checkbox"
|
checked={selectedWorkflowSteps.includes(step.id)}
|
||||||
checked={selectedWorkflowSteps.includes(step.id)}
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
setSelectedWorkflowSteps((prev) =>
|
||||||
setSelectedWorkflowSteps((prev) =>
|
e.target.checked
|
||||||
e.target.checked
|
? [...prev, step.id]
|
||||||
? [...prev, step.id]
|
: prev.filter((id) => id !== step.id)
|
||||||
: prev.filter((id) => id !== step.id)
|
);
|
||||||
);
|
}}
|
||||||
}}
|
disabled={isSubmitting}
|
||||||
disabled={isSubmitting}
|
style={{ marginTop: "2px" }}
|
||||||
style={{ marginTop: "2px" }}
|
/>
|
||||||
/>
|
<div>
|
||||||
<div>
|
<span style={{ fontWeight: 500, fontSize: "13px" }}>{step.name}</span>
|
||||||
<span style={{ fontWeight: 500, fontSize: "13px" }}>{step.name}</span>
|
<div style={{ fontSize: "12px", color: "var(--text-secondary)", marginTop: "2px" }}>
|
||||||
<div style={{ fontSize: "12px", color: "var(--text-secondary)", marginTop: "2px" }}>
|
{step.description}
|
||||||
{step.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</div>
|
||||||
))}
|
</label>
|
||||||
</div>
|
))}
|
||||||
|
<label
|
||||||
|
key="browser-verification"
|
||||||
|
className="checkbox-label"
|
||||||
|
style={{ display: "flex", alignItems: "flex-start", gap: "8px" }}
|
||||||
|
data-testid="browser-verification-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedWorkflowSteps.includes("browser-verification")}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSelectedWorkflowSteps((prev) =>
|
||||||
|
e.target.checked
|
||||||
|
? [...prev, "browser-verification"]
|
||||||
|
: prev.filter((id) => id !== "browser-verification")
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
style={{ marginTop: "2px" }}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<span style={{ fontWeight: 500, fontSize: "13px" }}>
|
||||||
|
<Globe size={14} style={{ verticalAlign: "middle", marginRight: "4px" }} />
|
||||||
|
Browser Verification
|
||||||
|
</span>
|
||||||
|
<div style={{ fontSize: "12px", color: "var(--text-secondary)", marginTop: "2px" }}>
|
||||||
|
Verify web application functionality using browser automation (agent-browser)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>AI-assisted creation</label>
|
<label>AI-assisted creation</label>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ vi.mock("lucide-react", () => ({
|
|||||||
Zap: () => null,
|
Zap: () => null,
|
||||||
ChevronDown: () => null,
|
ChevronDown: () => null,
|
||||||
ChevronUp: () => null,
|
ChevronUp: () => null,
|
||||||
|
Globe: () => null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock the api module
|
// Mock the api module
|
||||||
@@ -740,4 +741,89 @@ describe("InlineCreateCard button visibility when collapsed", () => {
|
|||||||
// Footer should now be in the DOM
|
// Footer should now be in the DOM
|
||||||
expect(document.getElementById("inline-create-controls")).toBeTruthy();
|
expect(document.getElementById("inline-create-controls")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("browser verification", () => {
|
||||||
|
it("shows browser verification button when expanded", () => {
|
||||||
|
const { props } = renderCard();
|
||||||
|
|
||||||
|
// Button should not be visible when collapsed
|
||||||
|
expect(screen.queryByTestId("browser-verification-toggle")).toBeNull();
|
||||||
|
|
||||||
|
// Expand the card
|
||||||
|
expandCard();
|
||||||
|
|
||||||
|
// Button should now be visible
|
||||||
|
expect(screen.getByTestId("browser-verification-toggle")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toggles browser verification when button is clicked", () => {
|
||||||
|
const { props } = renderCard();
|
||||||
|
expandCard();
|
||||||
|
|
||||||
|
const button = screen.getByTestId("browser-verification-toggle");
|
||||||
|
|
||||||
|
// Initially not active
|
||||||
|
expect(button).not.toHaveClass("btn-active");
|
||||||
|
|
||||||
|
// Click to enable
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(button).toHaveClass("btn-active");
|
||||||
|
|
||||||
|
// Click to disable
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(button).not.toHaveClass("btn-active");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes browser-verification in enabledWorkflowSteps when submitting with browser verification enabled", async () => {
|
||||||
|
const mockOnSubmit = vi.fn().mockResolvedValue(createMockTask());
|
||||||
|
const { props } = renderCard([], { onSubmit: mockOnSubmit });
|
||||||
|
expandCard();
|
||||||
|
|
||||||
|
// Enable browser verification
|
||||||
|
fireEvent.click(screen.getByTestId("browser-verification-toggle"));
|
||||||
|
|
||||||
|
// Fill in description
|
||||||
|
const textarea = screen.getByPlaceholderText("What needs to be done?");
|
||||||
|
fireEvent.change(textarea, { target: { value: "Test task with browser verification" } });
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
fireEvent.click(screen.getByTestId("save-button"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockOnSubmit).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
enabledWorkflowSteps: ["browser-verification"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resets browser verification state after successful submission", async () => {
|
||||||
|
const mockOnSubmit = vi.fn().mockResolvedValue(createMockTask({ id: "FN-042" }));
|
||||||
|
const { props } = renderCard([], { onSubmit: mockOnSubmit });
|
||||||
|
expandCard();
|
||||||
|
|
||||||
|
// Enable browser verification
|
||||||
|
fireEvent.click(screen.getByTestId("browser-verification-toggle"));
|
||||||
|
|
||||||
|
// Verify button is active
|
||||||
|
expect(screen.getByTestId("browser-verification-toggle")).toHaveClass("btn-active");
|
||||||
|
|
||||||
|
// Fill in description and submit
|
||||||
|
const textarea = screen.getByPlaceholderText("What needs to be done?");
|
||||||
|
fireEvent.change(textarea, { target: { value: "Task to complete" } });
|
||||||
|
fireEvent.click(screen.getByTestId("save-button"));
|
||||||
|
|
||||||
|
// Wait for submission to complete
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockOnSubmit).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Collapse and expand to reset
|
||||||
|
expandCard();
|
||||||
|
|
||||||
|
// Browser verification should be reset (button not active)
|
||||||
|
expect(screen.getByTestId("browser-verification-toggle")).not.toHaveClass("btn-active");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|||||||
import { NewTaskModal } from "../NewTaskModal";
|
import { NewTaskModal } from "../NewTaskModal";
|
||||||
import type { Task, Column } from "@fusion/core";
|
import type { Task, Column } from "@fusion/core";
|
||||||
|
|
||||||
|
// Mock lucide-react
|
||||||
|
vi.mock("lucide-react", () => ({
|
||||||
|
Sparkles: () => null,
|
||||||
|
Globe: () => null,
|
||||||
|
}));
|
||||||
|
|
||||||
// Mock the api module
|
// Mock the api module
|
||||||
vi.mock("../../api", () => ({
|
vi.mock("../../api", () => ({
|
||||||
uploadAttachment: vi.fn().mockResolvedValue({}),
|
uploadAttachment: vi.fn().mockResolvedValue({}),
|
||||||
@@ -233,4 +239,75 @@ describe("NewTaskModal", () => {
|
|||||||
const createButton = screen.getByRole("button", { name: "Create Task" });
|
const createButton = screen.getByRole("button", { name: "Create Task" });
|
||||||
expect(createButton).not.toBeDisabled();
|
expect(createButton).not.toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Browser verification tests
|
||||||
|
describe("browser verification", () => {
|
||||||
|
it("shows browser verification checkbox", () => {
|
||||||
|
renderNewTaskModal();
|
||||||
|
|
||||||
|
expect(screen.getByTestId("browser-verification-checkbox")).toBeTruthy();
|
||||||
|
expect(screen.getByText("Browser Verification")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds browser-verification to selected workflow steps when checkbox is checked", async () => {
|
||||||
|
const { props } = renderNewTaskModal();
|
||||||
|
|
||||||
|
const checkbox = screen.getByTestId("browser-verification-checkbox").querySelector('input[type="checkbox"]') as HTMLInputElement;
|
||||||
|
fireEvent.click(checkbox);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(checkbox.checked).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const descTextarea = screen.getByLabelText(/Description/i);
|
||||||
|
fireEvent.change(descTextarea, { target: { value: "Test task" } });
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(props.onCreateTask).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
enabledWorkflowSteps: expect.arrayContaining(["browser-verification"]),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes browser-verification from selected workflow steps when checkbox is unchecked", async () => {
|
||||||
|
renderNewTaskModal();
|
||||||
|
|
||||||
|
const checkbox = screen.getByTestId("browser-verification-checkbox").querySelector('input[type="checkbox"]') as HTMLInputElement;
|
||||||
|
|
||||||
|
// Check then uncheck
|
||||||
|
fireEvent.click(checkbox);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(checkbox.checked).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(checkbox);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(checkbox.checked).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes browser-verification in enabledWorkflowSteps when submitting with checkbox checked", async () => {
|
||||||
|
const { props } = renderNewTaskModal();
|
||||||
|
|
||||||
|
const checkbox = screen.getByTestId("browser-verification-checkbox").querySelector('input[type="checkbox"]') as HTMLInputElement;
|
||||||
|
fireEvent.click(checkbox);
|
||||||
|
|
||||||
|
const descTextarea = screen.getByLabelText(/Description/i);
|
||||||
|
fireEvent.change(descTextarea, { target: { value: "Browser test task" } });
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(props.onCreateTask).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
enabledWorkflowSteps: ["browser-verification"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user