refactor(automation): remove browser automation, use OS-level automation only
This commit is contained in:
@@ -1,120 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import * as path from 'path';
|
||||
import { resolveFixturesPath } from '@/apps/companion/main/di-container';
|
||||
|
||||
describe('DIContainer path resolution', () => {
|
||||
describe('resolveFixturesPath', () => {
|
||||
describe('built mode (with /dist/ in path)', () => {
|
||||
it('should resolve fixtures path to monorepo root when dirname is apps/companion/dist/main', () => {
|
||||
// Given: The dirname as it would be in built Electron runtime (apps/companion/dist/main)
|
||||
const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/dist/main';
|
||||
const relativePath = './resources/iracing-hosted-sessions';
|
||||
|
||||
// When: Resolving the fixtures path
|
||||
const resolved = resolveFixturesPath(relativePath, mockDirname);
|
||||
|
||||
// Then: Should resolve to monorepo root (4 levels up from apps/companion/dist/main)
|
||||
// Level 0: apps/companion/dist/main (__dirname)
|
||||
// Level 1: apps/companion/dist (../)
|
||||
// Level 2: apps/companion (../../)
|
||||
// Level 3: apps (../../../)
|
||||
// Level 4: gridpilot (monorepo root) (../../../../) ← CORRECT
|
||||
const expectedPath = '/Users/test/Projects/gridpilot/resources/iracing-hosted-sessions';
|
||||
expect(resolved).toBe(expectedPath);
|
||||
});
|
||||
|
||||
it('should navigate exactly 4 levels up in built mode', () => {
|
||||
// Given: A path with /dist/ that demonstrates the 4-level navigation
|
||||
const mockDirname = '/level4/level3/dist/level1';
|
||||
const relativePath = './target';
|
||||
|
||||
// When: Resolving the fixtures path
|
||||
const resolved = resolveFixturesPath(relativePath, mockDirname);
|
||||
|
||||
// Then: Should resolve to the path 4 levels up (root /)
|
||||
expect(resolved).toBe('/target');
|
||||
});
|
||||
|
||||
it('should work with different relative path formats in built mode', () => {
|
||||
// Given: Various relative path formats
|
||||
const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/dist/main';
|
||||
|
||||
// When/Then: Different relative formats should all work
|
||||
expect(resolveFixturesPath('resources/fixtures', mockDirname))
|
||||
.toBe('/Users/test/Projects/gridpilot/resources/fixtures');
|
||||
|
||||
expect(resolveFixturesPath('./resources/fixtures', mockDirname))
|
||||
.toBe('/Users/test/Projects/gridpilot/resources/fixtures');
|
||||
});
|
||||
});
|
||||
|
||||
describe('dev mode (without /dist/ in path)', () => {
|
||||
it('should resolve fixtures path to monorepo root when dirname is apps/companion/main', () => {
|
||||
// Given: The dirname as it would be in dev mode (apps/companion/main)
|
||||
const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/main';
|
||||
const relativePath = './resources/iracing-hosted-sessions';
|
||||
|
||||
// When: Resolving the fixtures path
|
||||
const resolved = resolveFixturesPath(relativePath, mockDirname);
|
||||
|
||||
// Then: Should resolve to monorepo root (3 levels up from apps/companion/main)
|
||||
// Level 0: apps/companion/main (__dirname)
|
||||
// Level 1: apps/companion (../)
|
||||
// Level 2: apps (../../)
|
||||
// Level 3: gridpilot (monorepo root) (../../../) ← CORRECT
|
||||
const expectedPath = '/Users/test/Projects/gridpilot/resources/iracing-hosted-sessions';
|
||||
expect(resolved).toBe(expectedPath);
|
||||
});
|
||||
|
||||
it('should navigate exactly 3 levels up in dev mode', () => {
|
||||
// Given: A path without /dist/ that demonstrates the 3-level navigation
|
||||
const mockDirname = '/level3/level2/level1';
|
||||
const relativePath = './target';
|
||||
|
||||
// When: Resolving the fixtures path
|
||||
const resolved = resolveFixturesPath(relativePath, mockDirname);
|
||||
|
||||
// Then: Should resolve to the path 3 levels up (root /)
|
||||
expect(resolved).toBe('/target');
|
||||
});
|
||||
|
||||
it('should work with different relative path formats in dev mode', () => {
|
||||
// Given: Various relative path formats
|
||||
const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/main';
|
||||
|
||||
// When/Then: Different relative formats should all work
|
||||
expect(resolveFixturesPath('resources/fixtures', mockDirname))
|
||||
.toBe('/Users/test/Projects/gridpilot/resources/fixtures');
|
||||
|
||||
expect(resolveFixturesPath('./resources/fixtures', mockDirname))
|
||||
.toBe('/Users/test/Projects/gridpilot/resources/fixtures');
|
||||
});
|
||||
});
|
||||
|
||||
describe('absolute paths', () => {
|
||||
it('should return absolute paths unchanged in built mode', () => {
|
||||
// Given: An absolute path
|
||||
const absolutePath = '/some/absolute/path/to/fixtures';
|
||||
const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/dist/main';
|
||||
|
||||
// When: Resolving an absolute path
|
||||
const resolved = resolveFixturesPath(absolutePath, mockDirname);
|
||||
|
||||
// Then: Should return the absolute path unchanged
|
||||
expect(resolved).toBe(absolutePath);
|
||||
});
|
||||
|
||||
it('should return absolute paths unchanged in dev mode', () => {
|
||||
// Given: An absolute path
|
||||
const absolutePath = '/some/absolute/path/to/fixtures';
|
||||
const mockDirname = '/Users/test/Projects/gridpilot/apps/companion/main';
|
||||
|
||||
// When: Resolving an absolute path
|
||||
const resolved = resolveFixturesPath(absolutePath, mockDirname);
|
||||
|
||||
// Then: Should return the absolute path unchanged
|
||||
expect(resolved).toBe(absolutePath);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,386 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { BrowserDevToolsAdapter, DevToolsConfig } from '../../../packages/infrastructure/adapters/automation/BrowserDevToolsAdapter';
|
||||
import { StepId } from '../../../packages/domain/value-objects/StepId';
|
||||
import {
|
||||
IRacingSelectorMap,
|
||||
getStepSelectors,
|
||||
getStepName,
|
||||
isModalStep,
|
||||
} from '../../../packages/infrastructure/adapters/automation/selectors/IRacingSelectorMap';
|
||||
|
||||
// Mock puppeteer-core
|
||||
vi.mock('puppeteer-core', () => {
|
||||
const mockPage = {
|
||||
url: vi.fn().mockReturnValue('https://members-ng.iracing.com/web/racing/hosted'),
|
||||
goto: vi.fn().mockResolvedValue(undefined),
|
||||
$: vi.fn().mockResolvedValue({
|
||||
click: vi.fn().mockResolvedValue(undefined),
|
||||
type: vi.fn().mockResolvedValue(undefined),
|
||||
}),
|
||||
click: vi.fn().mockResolvedValue(undefined),
|
||||
type: vi.fn().mockResolvedValue(undefined),
|
||||
waitForSelector: vi.fn().mockResolvedValue(undefined),
|
||||
setDefaultTimeout: vi.fn(),
|
||||
screenshot: vi.fn().mockResolvedValue(undefined),
|
||||
content: vi.fn().mockResolvedValue('<html></html>'),
|
||||
waitForNetworkIdle: vi.fn().mockResolvedValue(undefined),
|
||||
evaluate: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
const mockBrowser = {
|
||||
pages: vi.fn().mockResolvedValue([mockPage]),
|
||||
disconnect: vi.fn(),
|
||||
};
|
||||
|
||||
return {
|
||||
default: {
|
||||
connect: vi.fn().mockResolvedValue(mockBrowser),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Mock global fetch for CDP endpoint discovery
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
json: vi.fn().mockResolvedValue({
|
||||
webSocketDebuggerUrl: 'ws://127.0.0.1:9222/devtools/browser/mock-id',
|
||||
}),
|
||||
});
|
||||
|
||||
describe('BrowserDevToolsAdapter', () => {
|
||||
let adapter: BrowserDevToolsAdapter;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
adapter = new BrowserDevToolsAdapter({
|
||||
debuggingPort: 9222,
|
||||
defaultTimeout: 5000,
|
||||
typingDelay: 10,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (adapter.isConnected()) {
|
||||
await adapter.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
describe('instantiation', () => {
|
||||
it('should create adapter with default config', () => {
|
||||
const defaultAdapter = new BrowserDevToolsAdapter();
|
||||
expect(defaultAdapter).toBeInstanceOf(BrowserDevToolsAdapter);
|
||||
expect(defaultAdapter.isConnected()).toBe(false);
|
||||
});
|
||||
|
||||
it('should create adapter with custom config', () => {
|
||||
const customConfig: DevToolsConfig = {
|
||||
debuggingPort: 9333,
|
||||
defaultTimeout: 10000,
|
||||
typingDelay: 100,
|
||||
waitForNetworkIdle: false,
|
||||
};
|
||||
const customAdapter = new BrowserDevToolsAdapter(customConfig);
|
||||
expect(customAdapter).toBeInstanceOf(BrowserDevToolsAdapter);
|
||||
});
|
||||
|
||||
it('should create adapter with explicit WebSocket endpoint', () => {
|
||||
const wsAdapter = new BrowserDevToolsAdapter({
|
||||
browserWSEndpoint: 'ws://127.0.0.1:9222/devtools/browser/test-id',
|
||||
});
|
||||
expect(wsAdapter).toBeInstanceOf(BrowserDevToolsAdapter);
|
||||
});
|
||||
});
|
||||
|
||||
describe('connect/disconnect', () => {
|
||||
it('should connect to browser via debugging port', async () => {
|
||||
await adapter.connect();
|
||||
expect(adapter.isConnected()).toBe(true);
|
||||
});
|
||||
|
||||
it('should disconnect from browser without closing it', async () => {
|
||||
await adapter.connect();
|
||||
expect(adapter.isConnected()).toBe(true);
|
||||
|
||||
await adapter.disconnect();
|
||||
expect(adapter.isConnected()).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle multiple connect calls gracefully', async () => {
|
||||
await adapter.connect();
|
||||
await adapter.connect(); // Should not throw
|
||||
expect(adapter.isConnected()).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle disconnect when not connected', async () => {
|
||||
await adapter.disconnect(); // Should not throw
|
||||
expect(adapter.isConnected()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('navigateToPage', () => {
|
||||
beforeEach(async () => {
|
||||
await adapter.connect();
|
||||
});
|
||||
|
||||
it('should navigate to URL successfully', async () => {
|
||||
const result = await adapter.navigateToPage('https://members-ng.iracing.com');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.url).toBe('https://members-ng.iracing.com');
|
||||
expect(result.loadTime).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
it('should return error when not connected', async () => {
|
||||
await adapter.disconnect();
|
||||
|
||||
await expect(adapter.navigateToPage('https://example.com'))
|
||||
.rejects.toThrow('Not connected to browser');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fillFormField', () => {
|
||||
beforeEach(async () => {
|
||||
await adapter.connect();
|
||||
});
|
||||
|
||||
it('should fill form field successfully', async () => {
|
||||
const result = await adapter.fillFormField('input[name="sessionName"]', 'Test Session');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.fieldName).toBe('input[name="sessionName"]');
|
||||
expect(result.valueSet).toBe('Test Session');
|
||||
});
|
||||
|
||||
it('should return error for non-existent field', async () => {
|
||||
// Re-mock to return null for element lookup
|
||||
const puppeteer = await import('puppeteer-core');
|
||||
const mockBrowser = await puppeteer.default.connect({} as any);
|
||||
const pages = await mockBrowser.pages();
|
||||
const mockPage = pages[0] as any;
|
||||
mockPage.$.mockResolvedValueOnce(null);
|
||||
|
||||
const result = await adapter.fillFormField('input[name="nonexistent"]', 'value');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Field not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('clickElement', () => {
|
||||
beforeEach(async () => {
|
||||
await adapter.connect();
|
||||
});
|
||||
|
||||
it('should click element successfully', async () => {
|
||||
const result = await adapter.clickElement('.btn-primary');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.target).toBe('.btn-primary');
|
||||
});
|
||||
});
|
||||
|
||||
describe('waitForElement', () => {
|
||||
beforeEach(async () => {
|
||||
await adapter.connect();
|
||||
});
|
||||
|
||||
it('should wait for element and find it', async () => {
|
||||
const result = await adapter.waitForElement('#create-race-modal', 5000);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.found).toBe(true);
|
||||
expect(result.target).toBe('#create-race-modal');
|
||||
});
|
||||
|
||||
it('should return not found when element does not appear', async () => {
|
||||
// Re-mock to throw timeout error
|
||||
const puppeteer = await import('puppeteer-core');
|
||||
const mockBrowser = await puppeteer.default.connect({} as any);
|
||||
const pages = await mockBrowser.pages();
|
||||
const mockPage = pages[0] as any;
|
||||
mockPage.waitForSelector.mockRejectedValueOnce(new Error('Timeout'));
|
||||
|
||||
const result = await adapter.waitForElement('#nonexistent', 100);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.found).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleModal', () => {
|
||||
beforeEach(async () => {
|
||||
await adapter.connect();
|
||||
});
|
||||
|
||||
it('should handle modal for step 6 (SET_ADMINS)', async () => {
|
||||
const stepId = StepId.create(6);
|
||||
const result = await adapter.handleModal(stepId, 'open');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.stepId).toBe(6);
|
||||
expect(result.action).toBe('open');
|
||||
});
|
||||
|
||||
it('should handle modal for step 9 (ADD_CAR)', async () => {
|
||||
const stepId = StepId.create(9);
|
||||
const result = await adapter.handleModal(stepId, 'close');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.stepId).toBe(9);
|
||||
expect(result.action).toBe('close');
|
||||
});
|
||||
|
||||
it('should handle modal for step 12 (ADD_TRACK)', async () => {
|
||||
const stepId = StepId.create(12);
|
||||
const result = await adapter.handleModal(stepId, 'search');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.stepId).toBe(12);
|
||||
});
|
||||
|
||||
it('should return error for non-modal step', async () => {
|
||||
const stepId = StepId.create(4); // RACE_INFORMATION is not a modal step
|
||||
const result = await adapter.handleModal(stepId, 'open');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('not a modal step');
|
||||
});
|
||||
|
||||
it('should return error for unknown action', async () => {
|
||||
const stepId = StepId.create(6);
|
||||
const result = await adapter.handleModal(stepId, 'unknown_action');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Unknown modal action');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('IRacingSelectorMap', () => {
|
||||
describe('common selectors', () => {
|
||||
it('should have all required common selectors', () => {
|
||||
expect(IRacingSelectorMap.common.mainModal).toBeDefined();
|
||||
expect(IRacingSelectorMap.common.modalDialog).toBeDefined();
|
||||
expect(IRacingSelectorMap.common.modalContent).toBeDefined();
|
||||
expect(IRacingSelectorMap.common.checkoutButton).toBeDefined();
|
||||
expect(IRacingSelectorMap.common.wizardContainer).toBeDefined();
|
||||
expect(IRacingSelectorMap.common.wizardSidebar).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have iRacing-specific URLs', () => {
|
||||
expect(IRacingSelectorMap.urls.base).toContain('iracing.com');
|
||||
expect(IRacingSelectorMap.urls.hostedRacing).toContain('hosted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('step selectors', () => {
|
||||
it('should have selectors for all 18 steps', () => {
|
||||
for (let i = 1; i <= 18; i++) {
|
||||
expect(IRacingSelectorMap.steps[i]).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should have wizard navigation for most steps', () => {
|
||||
// Steps that have wizard navigation
|
||||
const stepsWithWizardNav = [4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18];
|
||||
|
||||
for (const stepNum of stepsWithWizardNav) {
|
||||
const selectors = IRacingSelectorMap.steps[stepNum];
|
||||
expect(selectors.wizardNav || selectors.sidebarLink).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should have modal selectors for modal steps (6, 9, 12)', () => {
|
||||
expect(IRacingSelectorMap.steps[6].modal).toBeDefined();
|
||||
expect(IRacingSelectorMap.steps[9].modal).toBeDefined();
|
||||
expect(IRacingSelectorMap.steps[12].modal).toBeDefined();
|
||||
});
|
||||
|
||||
it('should NOT have checkout button in step 18 (safety)', () => {
|
||||
const step18 = IRacingSelectorMap.steps[18];
|
||||
expect(step18.buttons?.checkout).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStepSelectors', () => {
|
||||
it('should return selectors for valid step', () => {
|
||||
const selectors = getStepSelectors(4);
|
||||
expect(selectors).toBeDefined();
|
||||
expect(selectors?.container).toBe('#set-session-information');
|
||||
});
|
||||
|
||||
it('should return undefined for invalid step', () => {
|
||||
const selectors = getStepSelectors(99);
|
||||
expect(selectors).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isModalStep', () => {
|
||||
it('should return true for modal steps', () => {
|
||||
expect(isModalStep(6)).toBe(true);
|
||||
expect(isModalStep(9)).toBe(true);
|
||||
expect(isModalStep(12)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-modal steps', () => {
|
||||
expect(isModalStep(1)).toBe(false);
|
||||
expect(isModalStep(4)).toBe(false);
|
||||
expect(isModalStep(18)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStepName', () => {
|
||||
it('should return correct step names', () => {
|
||||
expect(getStepName(1)).toBe('LOGIN');
|
||||
expect(getStepName(4)).toBe('RACE_INFORMATION');
|
||||
expect(getStepName(6)).toBe('SET_ADMINS');
|
||||
expect(getStepName(9)).toBe('ADD_CAR');
|
||||
expect(getStepName(12)).toBe('ADD_TRACK');
|
||||
expect(getStepName(18)).toBe('TRACK_CONDITIONS');
|
||||
});
|
||||
|
||||
it('should return UNKNOWN for invalid step', () => {
|
||||
expect(getStepName(99)).toContain('UNKNOWN');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration: Adapter with SelectorMap', () => {
|
||||
let adapter: BrowserDevToolsAdapter;
|
||||
|
||||
beforeEach(async () => {
|
||||
adapter = new BrowserDevToolsAdapter();
|
||||
await adapter.connect();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await adapter.disconnect();
|
||||
});
|
||||
|
||||
it('should use selector map for navigation', async () => {
|
||||
const selectors = getStepSelectors(4);
|
||||
expect(selectors?.sidebarLink).toBeDefined();
|
||||
|
||||
const result = await adapter.clickElement(selectors!.sidebarLink!);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should use selector map for form filling', async () => {
|
||||
const selectors = getStepSelectors(4);
|
||||
expect(selectors?.fields?.sessionName).toBeDefined();
|
||||
|
||||
const result = await adapter.fillFormField(
|
||||
selectors!.fields!.sessionName,
|
||||
'My Test Session'
|
||||
);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should use selector map for modal handling', async () => {
|
||||
const stepId = StepId.create(9);
|
||||
const selectors = getStepSelectors(9);
|
||||
expect(selectors?.modal).toBeDefined();
|
||||
|
||||
const result = await adapter.handleModal(stepId, 'open');
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -181,7 +181,7 @@ describe('MockBrowserAutomationAdapter Integration Tests', () => {
|
||||
const result = await adapter.executeStep(stepId, config);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.stepId).toBe(1);
|
||||
expect(result.metadata?.stepId).toBe(1);
|
||||
});
|
||||
|
||||
it('should execute step 6 (modal step)', async () => {
|
||||
@@ -195,8 +195,8 @@ describe('MockBrowserAutomationAdapter Integration Tests', () => {
|
||||
const result = await adapter.executeStep(stepId, config);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.stepId).toBe(6);
|
||||
expect(result.wasModalStep).toBe(true);
|
||||
expect(result.metadata?.stepId).toBe(6);
|
||||
expect(result.metadata?.wasModalStep).toBe(true);
|
||||
});
|
||||
|
||||
it('should execute step 18 (final step)', async () => {
|
||||
@@ -210,8 +210,8 @@ describe('MockBrowserAutomationAdapter Integration Tests', () => {
|
||||
const result = await adapter.executeStep(stepId, config);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.stepId).toBe(18);
|
||||
expect(result.shouldStop).toBe(true);
|
||||
expect(result.metadata?.stepId).toBe(18);
|
||||
expect(result.metadata?.shouldStop).toBe(true);
|
||||
});
|
||||
|
||||
it('should simulate realistic step execution times', async () => {
|
||||
@@ -224,7 +224,7 @@ describe('MockBrowserAutomationAdapter Integration Tests', () => {
|
||||
|
||||
const result = await adapter.executeStep(stepId, config);
|
||||
|
||||
expect(result.executionTime).toBeGreaterThan(0);
|
||||
expect(result.metadata?.executionTime).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -274,9 +274,9 @@ describe('MockBrowserAutomationAdapter Integration Tests', () => {
|
||||
|
||||
const result = await adapter.executeStep(stepId, config);
|
||||
|
||||
expect(result.metrics).toBeDefined();
|
||||
expect(result.metrics.totalDelay).toBeGreaterThan(0);
|
||||
expect(result.metrics.operationCount).toBeGreaterThan(0);
|
||||
expect(result.metadata).toBeDefined();
|
||||
expect(result.metadata?.totalDelay).toBeGreaterThan(0);
|
||||
expect(result.metadata?.operationCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user