explorbot 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/bin/explorbot-cli.ts +86 -15
- package/boat/api-tester/src/ai/curler-tools.ts +3 -3
- package/boat/api-tester/src/ai/curler.ts +1 -1
- package/boat/api-tester/src/apibot.ts +2 -2
- package/boat/api-tester/src/config.ts +1 -1
- package/dist/bin/explorbot-cli.js +85 -14
- package/dist/boat/api-tester/src/ai/curler-tools.js +2 -2
- package/dist/boat/api-tester/src/apibot.js +2 -2
- package/dist/package.json +2 -2
- package/dist/rules/navigator/output.md +9 -0
- package/dist/rules/navigator/verification-actions.md +2 -0
- package/dist/src/action-result.js +23 -1
- package/dist/src/action.js +46 -38
- package/dist/src/ai/bosun.js +16 -2
- package/dist/src/ai/conversation.js +39 -0
- package/dist/src/ai/experience-compactor.js +235 -50
- package/dist/src/ai/historian/codeceptjs.js +109 -0
- package/dist/src/ai/historian/experience.js +320 -0
- package/dist/src/ai/historian/mixin.js +2 -0
- package/dist/src/ai/historian/playwright.js +145 -0
- package/dist/src/ai/historian/utils.js +18 -0
- package/dist/src/ai/historian.js +19 -398
- package/dist/src/ai/navigator.js +133 -80
- package/dist/src/ai/pilot.js +254 -13
- package/dist/src/ai/planner/subpages.js +1 -30
- package/dist/src/ai/planner.js +33 -13
- package/dist/src/ai/provider.js +55 -18
- package/dist/src/ai/rerunner.js +3 -3
- package/dist/src/ai/researcher/deep-analysis.js +1 -1
- package/dist/src/ai/researcher/fingerprint-worker.js +1 -1
- package/dist/src/ai/researcher/locators.js +1 -1
- package/dist/src/ai/researcher/sections.js +8 -1
- package/dist/src/ai/researcher.js +43 -41
- package/dist/src/ai/rules.js +26 -14
- package/dist/src/ai/tester.js +90 -26
- package/dist/src/ai/tools.js +18 -10
- package/dist/src/api/request-store.js +20 -0
- package/dist/src/api/xhr-capture.js +19 -3
- package/dist/src/browser-server.js +16 -3
- package/dist/src/command-handler.js +1 -1
- package/dist/src/commands/add-rule-command.js +12 -9
- package/dist/src/commands/base-command.js +20 -0
- package/dist/src/commands/clean-command.js +3 -2
- package/dist/src/commands/compact-command.js +138 -0
- package/dist/src/commands/context-command.js +7 -1
- package/dist/src/commands/drill-command.js +4 -1
- package/dist/src/commands/experience-command.js +104 -0
- package/dist/src/commands/explore-command.js +54 -19
- package/dist/src/commands/freesail-command.js +2 -0
- package/dist/src/commands/index.js +7 -3
- package/dist/src/commands/init-command.js +11 -10
- package/dist/src/commands/learn-command.js +1 -1
- package/dist/src/commands/navigate-command.js +4 -1
- package/dist/src/commands/plan-clear-command.js +4 -1
- package/dist/src/commands/plan-command.js +43 -4
- package/dist/src/commands/plan-edit-command.js +1 -1
- package/dist/src/commands/plan-load-command.js +4 -1
- package/dist/src/commands/plan-reload-command.js +4 -1
- package/dist/src/commands/plan-save-command.js +20 -8
- package/dist/src/commands/rerun-command.js +4 -0
- package/dist/src/commands/research-command.js +5 -2
- package/dist/src/commands/start-command.js +5 -1
- package/dist/src/commands/test-command.js +7 -1
- package/dist/src/components/App.js +15 -5
- package/dist/src/execution-controller.js +13 -2
- package/dist/src/experience-tracker.js +174 -83
- package/dist/src/explorbot.js +31 -22
- package/dist/src/explorer.js +12 -5
- package/dist/src/observability.js +50 -99
- package/dist/src/playwright-recorder.js +309 -0
- package/dist/src/reporter.js +17 -2
- package/dist/src/stats.js +2 -0
- package/dist/src/suite.js +1 -1
- package/dist/src/test-plan.js +12 -0
- package/dist/src/utils/aria.js +37 -1
- package/dist/src/utils/error-page.js +30 -7
- package/dist/src/utils/logger.js +1 -1
- package/dist/src/utils/next-steps.js +37 -0
- package/dist/src/utils/rules-loader.js +1 -1
- package/dist/src/utils/test-files.js +1 -1
- package/dist/src/utils/url-matcher.js +50 -0
- package/package.json +2 -2
- package/rules/navigator/output.md +9 -0
- package/rules/navigator/verification-actions.md +2 -0
- package/src/action-result.ts +26 -1
- package/src/action.ts +44 -37
- package/src/ai/bosun.ts +16 -2
- package/src/ai/conversation.ts +37 -0
- package/src/ai/experience-compactor.ts +270 -63
- package/src/ai/historian/codeceptjs.ts +130 -0
- package/src/ai/historian/experience.ts +383 -0
- package/src/ai/historian/mixin.ts +4 -0
- package/src/ai/historian/playwright.ts +169 -0
- package/src/ai/historian/utils.ts +23 -0
- package/src/ai/historian.ts +35 -468
- package/src/ai/navigator.ts +140 -85
- package/src/ai/pilot.ts +259 -14
- package/src/ai/planner/subpages.ts +1 -24
- package/src/ai/planner.ts +34 -14
- package/src/ai/provider.ts +52 -18
- package/src/ai/rerunner.ts +3 -3
- package/src/ai/researcher/deep-analysis.ts +1 -1
- package/src/ai/researcher/fingerprint-worker.ts +1 -1
- package/src/ai/researcher/locators.ts +2 -2
- package/src/ai/researcher/sections.ts +7 -1
- package/src/ai/researcher.ts +47 -42
- package/src/ai/rules.ts +27 -14
- package/src/ai/task-agent.ts +1 -1
- package/src/ai/tester.ts +94 -26
- package/src/ai/tools.ts +53 -29
- package/src/api/request-store.ts +22 -0
- package/src/api/xhr-capture.ts +21 -3
- package/src/browser-server.ts +17 -3
- package/src/command-handler.ts +1 -1
- package/src/commands/add-rule-command.ts +13 -9
- package/src/commands/base-command.ts +26 -1
- package/src/commands/clean-command.ts +4 -3
- package/src/commands/compact-command.ts +156 -0
- package/src/commands/context-command.ts +8 -2
- package/src/commands/drill-command.ts +5 -2
- package/src/commands/experience-command.ts +125 -0
- package/src/commands/explore-command.ts +58 -21
- package/src/commands/freesail-command.ts +2 -0
- package/src/commands/index.ts +7 -3
- package/src/commands/init-command.ts +11 -10
- package/src/commands/learn-command.ts +2 -2
- package/src/commands/navigate-command.ts +5 -2
- package/src/commands/plan-clear-command.ts +5 -2
- package/src/commands/plan-command.ts +47 -5
- package/src/commands/plan-edit-command.ts +2 -2
- package/src/commands/plan-load-command.ts +5 -2
- package/src/commands/plan-reload-command.ts +5 -2
- package/src/commands/plan-save-command.ts +20 -9
- package/src/commands/rerun-command.ts +5 -0
- package/src/commands/research-command.ts +6 -3
- package/src/commands/start-command.ts +6 -2
- package/src/commands/test-command.ts +8 -2
- package/src/components/App.tsx +16 -5
- package/src/config.ts +6 -1
- package/src/execution-controller.ts +14 -3
- package/src/experience-tracker.ts +198 -100
- package/src/explorbot.ts +33 -23
- package/src/explorer.ts +14 -5
- package/src/observability.ts +50 -109
- package/src/playwright-recorder.ts +305 -0
- package/src/reporter.ts +17 -3
- package/src/stats.ts +4 -0
- package/src/suite.ts +1 -1
- package/src/test-plan.ts +12 -0
- package/src/utils/aria.ts +38 -1
- package/src/utils/error-page.ts +32 -7
- package/src/utils/logger.ts +1 -1
- package/src/utils/next-steps.ts +51 -0
- package/src/utils/rules-loader.ts +1 -1
- package/src/utils/test-files.ts +1 -1
- package/src/utils/url-matcher.ts +43 -0
|
@@ -113,6 +113,8 @@ For input field values, ALWAYS use I.seeInField() — never check value via CSS
|
|
|
113
113
|
Prefer text locators (label, name, placeholder) for form fields: I.seeInField('Search', 'value') over I.seeInField('input[name="search"]', 'value').
|
|
114
114
|
Only use locators that exist in the provided HTML or ARIA snapshot.
|
|
115
115
|
Verify exact conditions, not approximate matches.
|
|
116
|
+
NEVER use `:has-text(...)` inside a seeElement/dontSeeElement locator. Checking text inside an element is the job of I.see(text, context) — the `:has-text()` form duplicates that capability with a fragile selector.
|
|
117
|
+
NEVER emit two assertions that check the same fact with different shapes. `I.see(text, locator)` and `I.seeElement("<locator>:has-text('text')")` verify the same thing — pick one (prefer I.see). One claim, one assertion.
|
|
116
118
|
</verification_rules>
|
|
117
119
|
|
|
118
120
|
[DO NEVER USE OTHER CODECEPTJS COMMANDS THAN PROPOSED HERE]
|
package/src/action-result.ts
CHANGED
|
@@ -611,7 +611,7 @@ export class ActionResult implements ActionResultData {
|
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
613
|
if (processedParts.length > 0) {
|
|
614
|
-
pageDiff.htmlParts = processedParts;
|
|
614
|
+
pageDiff.htmlParts = collapseHtmlParts(processedParts);
|
|
615
615
|
}
|
|
616
616
|
}
|
|
617
617
|
|
|
@@ -629,6 +629,31 @@ export class ActionResult implements ActionResultData {
|
|
|
629
629
|
}
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
+
const HTML_PARTS_TOTAL_BUDGET = 8000;
|
|
633
|
+
const HTML_PARTS_COUNT_LIMIT = 8;
|
|
634
|
+
const HTML_PART_SUBTREE_BUDGET = 2000;
|
|
635
|
+
|
|
636
|
+
function collapseHtmlParts(parts: HtmlDiffPart[]): HtmlDiffPart[] {
|
|
637
|
+
const total = parts.reduce((sum, p) => sum + p.subtree.length, 0);
|
|
638
|
+
const fullPageReRender = total > HTML_PARTS_TOTAL_BUDGET || parts.length > HTML_PARTS_COUNT_LIMIT;
|
|
639
|
+
|
|
640
|
+
if (fullPageReRender) {
|
|
641
|
+
return parts.map((part) => ({
|
|
642
|
+
...part,
|
|
643
|
+
subtree: `<html><head></head><body>...collapsed (${part.subtree.length} chars, ${part.added.length} added, ${part.removed.length} removed)...</body></html>`,
|
|
644
|
+
}));
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return parts.map((part) => {
|
|
648
|
+
if (part.subtree.length <= HTML_PART_SUBTREE_BUDGET) return part;
|
|
649
|
+
const head = part.subtree.slice(0, HTML_PART_SUBTREE_BUDGET);
|
|
650
|
+
return {
|
|
651
|
+
...part,
|
|
652
|
+
subtree: `${head}...<!-- truncated ${part.subtree.length - HTML_PART_SUBTREE_BUDGET} chars -->`,
|
|
653
|
+
};
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
632
657
|
export class Diff {
|
|
633
658
|
private _htmlDiffResult: HtmlDiffResult | null = null;
|
|
634
659
|
private _ariaDiffResult: string | null = null;
|
package/src/action.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { ConfigParser, outputPath } from './config.js';
|
|
|
16
16
|
import type { ExplorbotConfig } from './config.js';
|
|
17
17
|
import type { UserResolveFunction } from './explorbot.ts';
|
|
18
18
|
import { Observability } from './observability.ts';
|
|
19
|
+
import type { PlaywrightRecorder } from './playwright-recorder.ts';
|
|
19
20
|
import type { StateManager } from './state-manager.js';
|
|
20
21
|
import { extractCodeBlocks } from './utils/code-extractor.js';
|
|
21
22
|
import { htmlCombinedSnapshot, minifyHtml } from './utils/html.js';
|
|
@@ -36,12 +37,16 @@ class Action {
|
|
|
36
37
|
private expectation: string | null = null;
|
|
37
38
|
public lastError: Error | null = null;
|
|
38
39
|
public playwrightHelper: any;
|
|
40
|
+
public playwrightGroupId: string | null = null;
|
|
41
|
+
public assertionSteps: Array<{ name: string; args: any[] }> = [];
|
|
42
|
+
private recorder?: PlaywrightRecorder;
|
|
39
43
|
|
|
40
|
-
constructor(actor: CodeceptJS.I, stateManager: StateManager) {
|
|
44
|
+
constructor(actor: CodeceptJS.I, stateManager: StateManager, recorder?: PlaywrightRecorder) {
|
|
41
45
|
this.actor = actor;
|
|
42
46
|
this.stateManager = stateManager;
|
|
43
47
|
this.config = ConfigParser.getInstance().getConfig();
|
|
44
48
|
this.playwrightHelper = container.helpers('Playwright');
|
|
49
|
+
this.recorder = recorder;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
async caputrePageWithScreenshot(): Promise<ActionResult> {
|
|
@@ -71,7 +76,14 @@ class Action {
|
|
|
71
76
|
const timestamp = Date.now();
|
|
72
77
|
const page = this.playwrightHelper.page;
|
|
73
78
|
const frame = this.playwrightHelper.frame;
|
|
74
|
-
|
|
79
|
+
await page?.waitForLoadState('domcontentloaded', { timeout: 10000 })?.catch(() => {});
|
|
80
|
+
const grabAll = () => Promise.all([(this.actor as any).grabSource(), (this.actor as any).grabTitle(), this.captureBrowserLogs()]);
|
|
81
|
+
const [html, title, browserLogs] = await grabAll().catch(async (err: Error) => {
|
|
82
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
83
|
+
if (!/navigating and changing the content/i.test(msg)) throw err;
|
|
84
|
+
await page?.waitForLoadState('domcontentloaded', { timeout: 10000 })?.catch(() => {});
|
|
85
|
+
return grabAll();
|
|
86
|
+
});
|
|
75
87
|
const url = page?.url() || (await (this.actor as any).grabCurrentUrl?.());
|
|
76
88
|
|
|
77
89
|
let screenshotFile: string | undefined = undefined;
|
|
@@ -218,7 +230,10 @@ class Action {
|
|
|
218
230
|
let codeString = code.replace(/^\(I\) => /, '').trim();
|
|
219
231
|
|
|
220
232
|
const executedSteps: string[] = [];
|
|
221
|
-
|
|
233
|
+
const assertionSteps: Array<{ name: string; args: any[] }> = [];
|
|
234
|
+
const stepListener = attachStepLogger(executedSteps, assertionSteps);
|
|
235
|
+
const groupId = this.recorder ? await this.recorder.beginAction(codeString) : null;
|
|
236
|
+
this.playwrightGroupId = groupId;
|
|
222
237
|
const activeSpan = Observability.getSpan();
|
|
223
238
|
const tracer = trace.getTracer('ai');
|
|
224
239
|
const stepSpan = activeSpan ? tracer.startSpan('codeceptjs.step', undefined, trace.setSpan(context.active(), activeSpan)) : null;
|
|
@@ -253,6 +268,7 @@ class Action {
|
|
|
253
268
|
this.stateManager.updateState(pageState, codeString);
|
|
254
269
|
|
|
255
270
|
this.actionResult = pageState;
|
|
271
|
+
this.assertionSteps = assertionSteps;
|
|
256
272
|
} catch (err) {
|
|
257
273
|
debugLog('Action error', errorToString(err));
|
|
258
274
|
error = err as Error;
|
|
@@ -260,9 +276,11 @@ class Action {
|
|
|
260
276
|
await recorder.reset();
|
|
261
277
|
await recorder.start();
|
|
262
278
|
}
|
|
279
|
+
this.assertionSteps = [];
|
|
263
280
|
throw err;
|
|
264
281
|
} finally {
|
|
265
|
-
|
|
282
|
+
if (groupId) await this.recorder!.endAction();
|
|
283
|
+
detachStepLogger(stepListener);
|
|
266
284
|
if (stepSpan) {
|
|
267
285
|
stepSpan.end();
|
|
268
286
|
}
|
|
@@ -407,41 +425,30 @@ function sleep(ms: number) {
|
|
|
407
425
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
408
426
|
}
|
|
409
427
|
|
|
410
|
-
|
|
411
|
-
let stepLoggerTarget: string[] | null = null;
|
|
428
|
+
const ASSERTION_STEP_NAMES = new Set(['see', 'dontSee', 'seeElement', 'dontSeeElement', 'seeInField', 'dontSeeInField', 'seeInCurrentUrl', 'dontSeeInCurrentUrl']);
|
|
412
429
|
|
|
413
|
-
|
|
414
|
-
if (!step?.toCode) {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
if (step.name?.startsWith('grab')) return;
|
|
418
|
-
const stepCode = step.toCode();
|
|
419
|
-
if (stepLoggerTarget) {
|
|
420
|
-
stepLoggerTarget.push(stepCode);
|
|
421
|
-
}
|
|
422
|
-
if (error) {
|
|
423
|
-
tag('step').log(step, error);
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
tag('step').log(step);
|
|
427
|
-
};
|
|
430
|
+
type StepListener = (step: any, error?: any) => void;
|
|
428
431
|
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return;
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
432
|
+
const attachStepLogger = (target: string[], assertionsTarget?: Array<{ name: string; args: any[] }>): StepListener => {
|
|
433
|
+
const listener: StepListener = (step, error) => {
|
|
434
|
+
if (!step?.toCode) return;
|
|
435
|
+
if (step.name?.startsWith('grab')) return;
|
|
436
|
+
target.push(step.toCode());
|
|
437
|
+
if (assertionsTarget && ASSERTION_STEP_NAMES.has(step.name)) {
|
|
438
|
+
assertionsTarget.push({ name: step.name, args: step.args || [] });
|
|
439
|
+
}
|
|
440
|
+
if (error) {
|
|
441
|
+
tag('step').log(step, error);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
tag('step').log(step);
|
|
445
|
+
};
|
|
446
|
+
codeceptjs.event.dispatcher.on(codeceptjs.event.step.passed, listener);
|
|
447
|
+
codeceptjs.event.dispatcher.on(codeceptjs.event.step.failed, listener);
|
|
448
|
+
return listener;
|
|
437
449
|
};
|
|
438
450
|
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
stepLoggerRegistered = false;
|
|
445
|
-
codeceptjs.event.dispatcher.off(codeceptjs.event.step.passed, stepLogger);
|
|
446
|
-
codeceptjs.event.dispatcher.off(codeceptjs.event.step.failed, stepLogger);
|
|
451
|
+
const detachStepLogger = (listener: StepListener) => {
|
|
452
|
+
codeceptjs.event.dispatcher.off(codeceptjs.event.step.passed, listener);
|
|
453
|
+
codeceptjs.event.dispatcher.off(codeceptjs.event.step.failed, listener);
|
|
447
454
|
};
|
package/src/ai/bosun.ts
CHANGED
|
@@ -10,8 +10,10 @@ import { Observability } from '../observability.ts';
|
|
|
10
10
|
import { Plan, Task, Test, TestResult } from '../test-plan.ts';
|
|
11
11
|
import { diffAriaSnapshots } from '../utils/aria.ts';
|
|
12
12
|
import { HooksRunner } from '../utils/hooks-runner.ts';
|
|
13
|
+
import { getCliName } from '../utils/cli-name.ts';
|
|
13
14
|
import { createDebug, tag } from '../utils/logger.ts';
|
|
14
15
|
import { loop, pause } from '../utils/loop.ts';
|
|
16
|
+
import { type NextStepSection, printNextSteps } from '../utils/next-steps.ts';
|
|
15
17
|
import type { Agent } from './agent.ts';
|
|
16
18
|
import type { Conversation } from './conversation.ts';
|
|
17
19
|
import type { Navigator } from './navigator.ts';
|
|
@@ -473,7 +475,11 @@ export class Bosun extends TaskAgent implements Agent {
|
|
|
473
475
|
const successfulInteractions = results.filter((r) => r.result === 'success' && r.code);
|
|
474
476
|
|
|
475
477
|
for (const interaction of successfulInteractions) {
|
|
476
|
-
|
|
478
|
+
experienceTracker.writeAction(actionResult, {
|
|
479
|
+
title: `Drill ${interaction.action}: ${interaction.component}`,
|
|
480
|
+
code: interaction.code!,
|
|
481
|
+
explanation: interaction.description,
|
|
482
|
+
});
|
|
477
483
|
}
|
|
478
484
|
|
|
479
485
|
if (successfulInteractions.length > 0) {
|
|
@@ -493,7 +499,15 @@ export class Bosun extends TaskAgent implements Agent {
|
|
|
493
499
|
const content = this.generateKnowledgeContent(state, successfulInteractions);
|
|
494
500
|
const result = knowledgeTracker.addKnowledge(knowledgePath, content);
|
|
495
501
|
|
|
496
|
-
|
|
502
|
+
const cli = getCliName();
|
|
503
|
+
const sections: NextStepSection[] = [
|
|
504
|
+
{
|
|
505
|
+
label: 'Knowledge',
|
|
506
|
+
path: result.filePath,
|
|
507
|
+
commands: [{ label: 'View matches', command: `${cli} knows ${knowledgePath}` }],
|
|
508
|
+
},
|
|
509
|
+
];
|
|
510
|
+
printNextSteps(sections);
|
|
497
511
|
}
|
|
498
512
|
|
|
499
513
|
private generateKnowledgeContent(state: any, interactions: InteractionResult[]): string {
|
package/src/ai/conversation.ts
CHANGED
|
@@ -11,6 +11,9 @@ export function toolExecutionLabel(input: Record<string, any> | undefined): stri
|
|
|
11
11
|
return input?.explanation || input?.assertion || input?.reason || input?.request || '';
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
const AUTO_COMPACT_ARIA_CHANGES_CUTOFF = 500;
|
|
15
|
+
const AUTO_COMPACT_TARGETED_HTML_CUTOFF = 500;
|
|
16
|
+
|
|
14
17
|
export class Conversation {
|
|
15
18
|
id: string;
|
|
16
19
|
messages: ModelMessage[];
|
|
@@ -132,6 +135,40 @@ export class Conversation {
|
|
|
132
135
|
this.autoTrimRules.set(tagName, maxLength);
|
|
133
136
|
}
|
|
134
137
|
|
|
138
|
+
compactToolResults(keepLastN: number): void {
|
|
139
|
+
const toolMessageIndexes: number[] = [];
|
|
140
|
+
for (let i = 0; i < this.messages.length; i++) {
|
|
141
|
+
if (this.messages[i].role === 'tool') toolMessageIndexes.push(i);
|
|
142
|
+
}
|
|
143
|
+
const compactUpTo = toolMessageIndexes.length - Math.max(0, keepLastN);
|
|
144
|
+
for (let k = 0; k < compactUpTo; k++) {
|
|
145
|
+
const message = this.messages[toolMessageIndexes[k]];
|
|
146
|
+
if (!Array.isArray(message.content)) continue;
|
|
147
|
+
for (const part of message.content) {
|
|
148
|
+
if (part.type !== 'tool-result') continue;
|
|
149
|
+
const rawOutput = part.output as Record<string, any> | undefined;
|
|
150
|
+
if (!rawOutput || rawOutput.type !== 'json' || !rawOutput.value || typeof rawOutput.value !== 'object') continue;
|
|
151
|
+
const value = rawOutput.value as Record<string, any>;
|
|
152
|
+
if (value.pageDiff && typeof value.pageDiff === 'object') {
|
|
153
|
+
const pageDiff = value.pageDiff as Record<string, any>;
|
|
154
|
+
if (Array.isArray(pageDiff.htmlParts)) {
|
|
155
|
+
pageDiff.htmlParts = undefined;
|
|
156
|
+
pageDiff.compacted = true;
|
|
157
|
+
}
|
|
158
|
+
if (typeof pageDiff.ariaChanges === 'string' && pageDiff.ariaChanges.length > AUTO_COMPACT_ARIA_CHANGES_CUTOFF) {
|
|
159
|
+
pageDiff.ariaChanges = `${pageDiff.ariaChanges.slice(0, AUTO_COMPACT_ARIA_CHANGES_CUTOFF)}...`;
|
|
160
|
+
}
|
|
161
|
+
if (typeof pageDiff.iframes === 'string') {
|
|
162
|
+
pageDiff.iframes = undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (typeof value.targetedHtml === 'string' && value.targetedHtml.length > AUTO_COMPACT_TARGETED_HTML_CUTOFF) {
|
|
166
|
+
value.targetedHtml = `${value.targetedHtml.slice(0, AUTO_COMPACT_TARGETED_HTML_CUTOFF)}...`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
135
172
|
hasTag(tagName: string, lastN?: number): boolean {
|
|
136
173
|
const escapedTag = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
137
174
|
const regex = new RegExp(`<${escapedTag}>`, 'g');
|