Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import fs from 'node:fs';
import { join } from 'node:path';
import { faker } from '@faker-js/faker';
import { context, trace } from '@opentelemetry/api';
import { highlight } from 'cli-highlight';
import { container, recorder } from 'codeceptjs';
import * as codeceptjs from 'codeceptjs';
import { hopeThat, retryTo, tryTo, within } from 'codeceptjs/lib/effects';
Expand All @@ -21,7 +20,7 @@ import type { PlaywrightRecorder } from './playwright-recorder.ts';
import type { StateManager } from './state-manager.js';
import { extractCodeBlocks } from './utils/code-extractor.js';
import { htmlCombinedSnapshot, minifyHtml } from './utils/html.js';
import { createDebug, log, setStepSpanParent, tag } from './utils/logger.js';
import { createDebug, setStepSpanParent, tag } from './utils/logger.js';
import { safeFilename } from './utils/strings.ts';
import { throttle } from './utils/throttle.ts';

Expand Down Expand Up @@ -296,7 +295,13 @@ class Action {
async expect(codeOrFunction: string | ((I: CodeceptJS.I) => void)): Promise<Action> {
const codeString = typeof codeOrFunction === 'string' ? codeOrFunction : codeOrFunction.toString();
this.expectation = codeString.toString();
log('Expecting', highlight(codeString, { language: 'javascript' }));
const expectationPreview = sanitizeCodeBlock(codeString)
.split('\n')
.map((line) => line.trim())
.filter(Boolean)
.slice(0, 2)
.join(' ');
tag('step').log(`Expecting: ${expectationPreview || 'assertion'}`);
try {
debugLog('Executing expectation:', codeString);

Expand Down
2 changes: 1 addition & 1 deletion src/ai/driller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export class Driller extends TaskAgent implements Agent {
this.allResults = [];

return Observability.run(`driller: ${currentState.url}`, { tags: ['driller'], sessionId: sessionName }, async () => {
tag('info').log(`Driller starting on ${currentState.url}`);
tag('step').log(`Drilling page: ${currentState.url}`);
await this.hooksRunner.runBeforeHook('driller', currentState.url);

const originalState = await this.captureAnnotatedState();
Expand Down
1 change: 0 additions & 1 deletion src/ai/planner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ export class Planner extends PlannerBase implements Agent {
this.freshStart = false;

setActivity(`${this.emoji} Planning...`, 'action');
tag('info').log(`Planning test scenarios for ${state.url}`);
if (style) tag('info').log(`Planning style: ${style}`);

const tags = ['planner'];
Expand Down
2 changes: 1 addition & 1 deletion src/ai/rerunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class Rerunner extends TaskAgent implements Agent {
return { total: 0, passed: 0, failed: 0, healed: 0 };
}

tag('info').log(`Re-running tests from: ${relative(process.cwd(), absPath)}`);
tag('step').log(`Re-running tests from: ${relative(process.cwd(), absPath)}`);
setActivity('🔄 Re-running tests...', 'action');

this.healedSteps = [];
Expand Down
6 changes: 3 additions & 3 deletions src/ai/researcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ export class Researcher extends ResearcherBase implements Agent {
if (!deep && !force) {
const similar = await findSimilarResearch(combinedHtml);
if (similar) {
tag('info').log('Similar research found, reusing cached result');
tag('substep').log('Similar research found, reusing cached result');
if (stateHash) saveResearch(stateHash, similar, combinedHtml);
tag('multiline').log(formatResearchSummary(similar));
tag('success').log(`Research complete! ${similar.length} characters (reused)`);
tag('success').log('Research complete (reused)');
await this.hooksRunner.runAfterHook('researcher', state.url);
return similar;
}
Expand Down Expand Up @@ -311,7 +311,7 @@ export class Researcher extends ResearcherBase implements Agent {
}

tag('multiline').log(formatResearchSummary(result.text, { visionUsed: this.hasScreenshotToAnalyze }));
tag('success').log(`Research complete! ${result.text.length} characters`);
tag('success').log('Research complete');
if (researchFile) tag('substep').log(`Research file saved to: ${researchFile}`);
if (this.actionResult?.screenshotFile) {
const screenshotPath = outputPath('states', this.actionResult.screenshotFile);
Expand Down
2 changes: 0 additions & 2 deletions src/ai/tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ export class Tester extends TaskAgent implements Agent {
const state = this.explorer.getStateManager().getCurrentState();
if (!state) throw new Error('No state found');

tag('info').log(`Testing scenario: ${task.scenario}`);
setActivity(`🧪 Testing: ${task.scenario}`, 'action');

this.previousUrl = null;
Expand Down Expand Up @@ -678,7 +677,6 @@ export class Tester extends TaskAgent implements Agent {
if (!task.hasFinished) {
task.finish(TestResult.FAILED);
}
tag('info').log(`Finished: ${task.scenario}`);

if (task.isSuccessful) {
tag('success').log(`Successful test: ${task.scenario}`);
Expand Down
51 changes: 42 additions & 9 deletions src/components/LogPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,40 @@ import { parseMarkdownToTerminal } from '../utils/markdown-terminal.js';

import { Box, Text } from 'ink';
import type { LogType, TaggedLogEntry } from '../utils/logger.js';
import { isDebugMode, registerLogPane, setVerboseMode, unregisterLogPane } from '../utils/logger.js';
import { isDebugMode, registerLogPane, unregisterLogPane } from '../utils/logger.js';

// marked.use(new markedTerminal());

type LogEntry = TaggedLogEntry;
type LogEntry = TaggedLogEntry & { collapsedCount?: number };

interface LogPaneProps {
verboseMode: boolean;
}

const LogPane: React.FC<LogPaneProps> = React.memo(({ verboseMode }) => {
const [logs, setLogs] = useState<TaggedLogEntry[]>([]);
const pendingLogsRef = React.useRef<TaggedLogEntry[]>([]);
const [logs, setLogs] = useState<LogEntry[]>([]);
const pendingLogsRef = React.useRef<LogEntry[]>([]);
const flushTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);

const MAX_MULTILINE_LINES = 16;
const MAX_STEP_LINES = 8;
const MAX_SUBSTEP_LINES = 6;

const formatCollapsedContent = useCallback((lines: string[], collapsedCount: number, label: string) => {
if (collapsedCount <= 0) {
return lines.join('\n');
}
return [`... ${collapsedCount} earlier ${label}`, ...lines].join('\n');
}, []);

const flushLogs = useCallback(() => {
if (pendingLogsRef.current.length === 0) return;

const newLogs = pendingLogsRef.current;
pendingLogsRef.current = [];
flushTimeoutRef.current = null;

setLogs((prevLogs: TaggedLogEntry[]) => {
setLogs((prevLogs: LogEntry[]) => {
const result = [...prevLogs];

for (const logEntry of newLogs) {
Expand All @@ -43,12 +54,35 @@ const LogPane: React.FC<LogPaneProps> = React.memo(({ verboseMode }) => {
continue;
}

if ((logEntry.type === 'step' || logEntry.type === 'substep') && lastLog.type === logEntry.type && Math.abs((lastLog.timestamp?.getTime() || 0) - (logEntry.timestamp?.getTime() || 0)) < 1500) {
const currentLines = String(logEntry.content)
.split('\n')
.filter((line) => line.length > 0);
const previousLines = String(lastLog.content)
.split('\n')
.filter((line) => line.length > 0);
const visiblePreviousLines = lastLog.collapsedCount ? previousLines.slice(1) : previousLines;
const maxLines = logEntry.type === 'step' ? MAX_STEP_LINES : MAX_SUBSTEP_LINES;
const mergedLines = [...visiblePreviousLines, ...currentLines];
const overflow = Math.max(0, mergedLines.length - maxLines);
const collapsedCount = (lastLog.collapsedCount || 0) + overflow;
const visibleLines = mergedLines.slice(-maxLines);
const label = logEntry.type === 'step' ? 'steps' : 'details';
result[result.length - 1] = {
...lastLog,
content: formatCollapsedContent(visibleLines, collapsedCount, label),
timestamp: logEntry.timestamp,
collapsedCount,
};
continue;
}

result.push(logEntry);
}

return result;
});
}, []);
}, [formatCollapsedContent]);

const addLog = useCallback(
(logEntry: TaggedLogEntry) => {
Expand Down Expand Up @@ -112,12 +146,11 @@ const LogPane: React.FC<LogPaneProps> = React.memo(({ verboseMode }) => {
const cleaned = stripAnsi(dedent(log.content));
const parsed = parseMarkdownToTerminal(cleaned);
const lines = parsed.split('\n');
const maxLines = 30;
const truncated = lines.length > maxLines ? `${lines.slice(0, maxLines).join('\n')}\n... (${lines.length - maxLines} more lines)` : cleaned;
const truncated = lines.length > MAX_MULTILINE_LINES ? `${lines.slice(0, MAX_MULTILINE_LINES).join('\n')}\n... (${lines.length - MAX_MULTILINE_LINES} more lines)` : parsed;
return (
<Box key={index} borderStyle="classic" borderLeft={false} borderRight={false} marginY={1} padding={1} borderColor="dim" overflow="hidden">
<Text color="gray" dimColor>
{parsed}
{truncated}
</Text>
</Box>
);
Expand Down
Loading