sync: update from private repo (2336949a)
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
oss-sync
2026-09-17 11:29:31 +00:00
parent 28f74c4e37
commit 57702b4f34
2139 changed files with 321270 additions and 17494 deletions
+37 -22
View File
@@ -8,7 +8,7 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import { ToolDef } from '../../llm/openai-compat.js';
import type { ToolContext, ToolResult } from './core.js';
import type { ToolContext, ToolResult } from './tool-context.js';
import { resolveAndGuard } from './core.js';
import { logger } from '../../logger.js';
import { loadConfig } from '../../config.js';
@@ -49,7 +49,6 @@ export interface ExpectItem {
}
interface ValidatedInput {
space: string;
app: string;
entry: string;
steps: Record<string, unknown>[];
@@ -67,11 +66,6 @@ export function validateInput(raw: Record<string, unknown>): ValidatedInput {
if (typeof app !== 'string' || !app.trim()) {
throw new Error('TestWorkspaceApp: "app" (string) is required');
}
const space = raw['space'];
if (typeof space !== 'string' || !space.trim()) {
throw new Error('TestWorkspaceApp: "space" (string) is required');
}
const entry =
typeof raw['entry'] === 'string' && raw['entry'].trim()
? raw['entry'].trim()
@@ -105,12 +99,12 @@ export function validateInput(raw: Record<string, unknown>): ValidatedInput {
}
}
const timeout_ms =
const timeout_ms = Math.min(120_000,
typeof raw['timeout_ms'] === 'number' && raw['timeout_ms'] > 0
? raw['timeout_ms']
: 30_000;
: 30_000);
return { space: space.trim(), app: app.trim(), entry, steps, seed_files, expect: expectRaw, timeout_ms };
return { app: app.trim(), entry, steps, seed_files, expect: expectRaw, timeout_ms };
}
// ---------------------------------------------------------------------------
@@ -122,6 +116,7 @@ export interface EvaluateExpectationsContext {
pageText: string;
/** Absolute path to the workspace root */
workspacePath: string;
targetedText?: Record<string, string>;
}
export interface ExpectationFailure {
@@ -147,7 +142,10 @@ export function evaluateExpectations(
if (exp.kind === 'text') {
// target is treated as a hint/CSS selector (informational), contains is checked against page text
const needle = exp.contains ?? '';
if (!ctx.pageText.includes(needle)) {
const haystack = exp.target && exp.target !== 'body'
? (ctx.targetedText?.[exp.target] ?? '')
: ctx.pageText;
if (!haystack.includes(needle)) {
failures.push(
`text[${i}]: target="${exp.target ?? ''}" — "${needle}" not found in page text`,
);
@@ -195,10 +193,6 @@ export const TOOL_DEFS: Record<string, ToolDef> = {
parameters: {
type: 'object',
properties: {
space: {
type: 'string',
description: 'テスト対象スペースの ID',
},
app: {
type: 'string',
description: 'apps/ 配下のアプリフォルダ名(パスではなく名前のみ)',
@@ -268,7 +262,7 @@ export const TOOL_DEFS: Record<string, ToolDef> = {
description: 'ブラウザ操作全体のタイムアウト(ミリ秒、デフォルト 30000)',
},
},
required: ['space', 'app'],
required: ['app'],
},
},
},
@@ -316,7 +310,7 @@ export async function executeTool(
return { output: (err as Error).message, isError: true };
}
const { space, app, entry, steps, seed_files, expect: expectations, timeout_ms } = validated;
const { app, entry, steps, seed_files, expect: expectations, timeout_ms } = validated;
const result: AppTestResult = {
ok: false,
@@ -348,7 +342,7 @@ export async function executeTool(
try {
// Build harness URL
const harnessUrl = buildHarnessUrl(port, { space, app, entry, token });
const harnessUrl = buildHarnessUrl(port, { space: spaceId, app, entry, token });
// Dynamically import browser module (avoids Playwright load when unused)
const { runPageActions } = await import('./browser.js');
@@ -366,6 +360,8 @@ export async function executeTool(
{ type: 'getText' },
// User steps
...(steps as unknown as BrowseWebAction[]),
...expectations.filter(item => item.kind === 'text' && item.target && item.target !== 'body')
.map(item => ({ type: 'getText' as const, selector: item.target })),
// Final screenshot
{ type: 'screenshot', value: `app-test-${app}-final.png` },
];
@@ -377,19 +373,38 @@ export async function executeTool(
if (a.type === 'screenshot' && a.segments === undefined) a.segments = false;
}
const pageResult = await runPageActions(harnessUrl, builtActions, ctx, {
isolatedContext: true,
extraAllowedHosts: ['localhost', '127.0.0.1'],
});
const timeoutController = new AbortController();
const timeoutHandle = setTimeout(() => timeoutController.abort(), timeout_ms);
let pageResult: Awaited<ReturnType<typeof runPageActions>>;
try {
pageResult = await runPageActions(harnessUrl, builtActions, ctx, {
isolatedContext: true,
extraAllowedHosts: ['localhost', '127.0.0.1'],
defaultToFirstChildFrame: true,
abortSignal: timeoutController.signal,
});
if (timeoutController.signal.aborted) throw new Error(`timed out after ${timeout_ms}ms`);
} catch (error) {
if (timeoutController.signal.aborted) throw new Error(`timed out after ${timeout_ms}ms`);
throw error;
} finally {
clearTimeout(timeoutHandle);
}
result.screenshots = pageResult.screenshots;
result.console_errors = pageResult.consoleErrors;
for (const error of result.console_errors) result.failures.push(`app diagnostic: ${error}`);
// Evaluate expectations using the extracted pure function
const pageText = pageResult.texts.join('\n');
const targetedText: Record<string, string> = {};
const targeted = expectations.filter(item => item.kind === 'text' && item.target && item.target !== 'body');
const captured = pageResult.texts.slice(-targeted.length);
targeted.forEach((item, index) => { targetedText[item.target] = captured[index] ?? ''; });
const expectFailures = evaluateExpectations(expectations, {
pageText,
workspacePath: ctx.workspacePath,
targetedText,
});
result.failures.push(...expectFailures);