Skip to content

Commit dd299af

Browse files
committed
refactor text extraction & generate text from raw response when missing
in qa
1 parent 45fbd89 commit dd299af

2 files changed

Lines changed: 259 additions & 114 deletions

File tree

src/replaycrawler.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { parseRx } from "./util/seeds.js";
2222
import { getFileOrUrlJson } from "./util/file_reader.js";
2323
import { WACZLoader } from "./util/wacz.js";
2424
import { CSR_CLUES } from "./util/clientSideRenderingClues.js";
25+
import { extractTextFromDOM } from "./util/textextract.js";
2526

2627
// RWP Replay Prefix
2728
const REPLAY_PREFIX = "http://localhost:9990/replay/w/replay/";
@@ -44,6 +45,7 @@ type ComparisonData = {
4445
comparison: {
4546
screenshotMatch?: number;
4647
textMatch?: number;
48+
rawTextMatch?: number;
4749
resourceCounts: {
4850
crawlGood?: number;
4951
crawlBad?: number;
@@ -467,6 +469,8 @@ export class ReplayCrawler extends Crawler {
467469

468470
await this.compareText(page, data, url, date);
469471

472+
await this.compareRawText(page, data, url, date);
473+
470474
await this.compareResources(page, data, url, date);
471475

472476
if (this.params.qaDetectClientSideRendering)
@@ -596,6 +600,112 @@ export class ReplayCrawler extends Crawler {
596600
}
597601
}
598602

603+
async compareRawText(page: Page, state: PageState, url: string, date: Date) {
604+
// Try to fetch the stored crawl-time raw response text
605+
let rawText = await this.fetchOrigText(
606+
page,
607+
"text-from-response",
608+
url,
609+
date.toISOString().replace(/[^\d]/g, ""),
610+
);
611+
612+
// If not stored (older archive), extract it from the archived raw response on-the-fly
613+
if (rawText === undefined) {
614+
logger.info(
615+
"Stored raw text not found, extracting from archived response",
616+
{ url },
617+
"replay",
618+
);
619+
rawText = await this.extractRawTextFromArchive(page, url, date);
620+
}
621+
622+
// Get the replay-time rendered text (extracted from browser after JS runs)
623+
const renderedText = state.text;
624+
625+
if (rawText === undefined || renderedText === undefined) {
626+
logger.warn(
627+
"Text missing for raw vs rendered comparison",
628+
{
629+
url,
630+
rawTextLen: rawText?.length,
631+
renderedTextLen: renderedText?.length,
632+
},
633+
"replay",
634+
);
635+
return;
636+
}
637+
638+
const dist = levenshtein(rawText, renderedText);
639+
const maxLen = Math.max(rawText.length, renderedText.length);
640+
641+
let matchPercent = 1.0;
642+
if (maxLen > 0) {
643+
matchPercent = (maxLen - dist) / maxLen;
644+
}
645+
logger.info("Raw vs Rendered Text Levenshtein Dist", {
646+
url,
647+
dist,
648+
matchPercent,
649+
maxLen,
650+
rawTextLen: rawText.length,
651+
renderedTextLen: renderedText.length,
652+
});
653+
654+
const pageInfo = this.pageInfos.get(page);
655+
if (pageInfo) {
656+
pageInfo.comparison.rawTextMatch = matchPercent;
657+
}
658+
}
659+
660+
async extractRawTextFromArchive(
661+
page: Page,
662+
url: string,
663+
date: Date,
664+
): Promise<string | undefined> {
665+
const timestamp = date.toISOString().slice(0, 19).replace(/[T:-]/g, "");
666+
// `if_` suffix ensures wabac.js serves the unaltered source
667+
const replayUrl = REPLAY_PREFIX + `${timestamp}if_/${url}`;
668+
669+
const frame = page.frames()[1];
670+
if (!frame) {
671+
logger.warn(
672+
"Replay frame missing for raw text extraction",
673+
{ url },
674+
"replay",
675+
);
676+
return undefined;
677+
}
678+
679+
const htmlContent = await frame.evaluate(async (url) => {
680+
const response = await fetch(url, {
681+
method: "GET",
682+
credentials: "include",
683+
});
684+
if (response.status !== 200) {
685+
return undefined;
686+
}
687+
return await response.text();
688+
}, replayUrl);
689+
690+
if (!htmlContent) {
691+
logger.warn(
692+
"Could not fetch HTML content for raw text extraction",
693+
{ url },
694+
"replay",
695+
);
696+
return undefined;
697+
}
698+
699+
// Extract text from the raw HTML using the shared extraction logic
700+
const { JSDOM } = await import("jsdom");
701+
const dom = new JSDOM(htmlContent);
702+
try {
703+
return extractTextFromDOM(dom.window.document);
704+
} finally {
705+
dom.window.close();
706+
}
707+
}
708+
599709
async compareResources(
600710
page: Page,
601711
state: PageState,

0 commit comments

Comments
 (0)