From f82bc90168f78519e83209bb83276ea773eaca73 Mon Sep 17 00:00:00 2001 From: DavertMik Date: Fri, 6 Feb 2026 05:56:29 +0200 Subject: [PATCH 1/3] Add error/404/500 page detection to Researcher Skip full AI-powered research when navigating to error pages by detecting them early. Uses a two-layer approach: 1. Fast heuristic check (zero cost): - Checks title, h1, h2 for error patterns (404, 500, 502, 503, 403) - Detects empty body HTML - Catches very small pages (< 500 chars in body) 2. AI prompt instruction as fallback: - Instructs AI to detect custom error pages semantically - Returns standardized error format instead of normal research Co-Authored-By: Claude Opus 4.5 --- src/ai/researcher.ts | 26 ++++++++++++++++++++++++++ src/utils/error-page.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/utils/error-page.ts diff --git a/src/ai/researcher.ts b/src/ai/researcher.ts index 962f69f..0a54816 100644 --- a/src/ai/researcher.ts +++ b/src/ai/researcher.ts @@ -15,6 +15,7 @@ import { type HtmlDiffResult, htmlDiff } from '../utils/html-diff.ts'; import { codeToMarkdown, isBodyEmpty } from '../utils/html.ts'; import { createDebug, pluralize, tag } from '../utils/logger.js'; import { collectInteractiveNodes, diffAriaSnapshots } from '../utils/aria.ts'; +import { isErrorPage } from '../utils/error-page.ts'; import { loop } from '../utils/loop.ts'; import type { Agent } from './agent.js'; import type { Conversation } from './conversation.js'; @@ -108,6 +109,20 @@ export class Researcher implements Agent { const isOnCurrentState = this.actionResult!.getStateHash() === this.stateManager.getCurrentState()?.hash; await this.ensureNavigated(state.url, screenshot && this.provider.hasVision()); + const errorCheck = isErrorPage(this.actionResult!); + if (errorCheck.isError) { + tag('warn').log(`Detected ${errorCheck.type} page at ${state.url}`); + return dedent` + ## Error Page Detected + + This appears to be a **${errorCheck.type}** page. + URL: ${state.url} + Title: ${this.actionResult!.title || 'N/A'} + + Research skipped. Navigate to a valid page to continue. + `; + } + debugLog('Researching web page:', this.actionResult!.url); this.hasScreenshotToAnalyze = screenshot && this.provider.hasVision() && isOnCurrentState; @@ -393,6 +408,17 @@ export class Researcher implements Agent { return dedent` Analyze this web page and provide a comprehensive research report in markdown format. + + IMPORTANT: First check if this looks like an error page (404, 500, access denied, + not found, server error, forbidden, or similar). If so, respond ONLY with: + + ## Error Page Detected + Type: [error type] + Reason: [what indicates this is an error page] + + Then stop - do not provide normal research output for error pages. + + ${this.buildResearchTaskPrompt()} URL: ${this.actionResult.url || 'Unknown'} diff --git a/src/utils/error-page.ts b/src/utils/error-page.ts new file mode 100644 index 0000000..f341426 --- /dev/null +++ b/src/utils/error-page.ts @@ -0,0 +1,40 @@ +import type { ActionResult } from '../action-result.js'; +import { isBodyEmpty } from './html.js'; + +export type ErrorPageResult = { + isError: boolean; + type?: '404' | '500' | '503' | '502' | '403' | 'empty'; +}; + +const ERROR_PATTERNS: { pattern: RegExp; type: '404' | '500' | '503' | '502' | '403' }[] = [ + { pattern: /\b404\b/, type: '404' }, + { pattern: /\b500\b/, type: '500' }, + { pattern: /\b503\b/, type: '503' }, + { pattern: /\b502\b/, type: '502' }, + { pattern: /\b403\b/, type: '403' }, +]; + +const SMALL_PAGE_THRESHOLD = 500; + +export function isErrorPage(actionResult: ActionResult): ErrorPageResult { + const checkFields = [actionResult.title, actionResult.h1, actionResult.h2].filter(Boolean); + + for (const field of checkFields) { + for (const { pattern, type } of ERROR_PATTERNS) { + if (pattern.test(field!)) { + return { isError: true, type }; + } + } + } + + if (!actionResult.html || isBodyEmpty(actionResult.html)) { + return { isError: true, type: 'empty' }; + } + + const bodyMatch = actionResult.html.match(/]*>([\s\S]*?)<\/body>/i); + if (bodyMatch && bodyMatch[1].trim().length < SMALL_PAGE_THRESHOLD) { + return { isError: true, type: 'empty' }; + } + + return { isError: false }; +} From 7d8fecef08ba228f008a6670b7a46df9950b3716 Mon Sep 17 00:00:00 2001 From: DavertMik Date: Fri, 6 Feb 2026 06:03:39 +0200 Subject: [PATCH 2/3] Improve error page detection with contextual patterns - Simplify return type to boolean (no type field needed) - Require error context for numeric codes (e.g., "404 error", "error 500") to prevent false positives like "Room 404" or "Order #500" - Add text-based patterns: "Page Not Found", "Server Error", "Access Denied" - Add comprehensive unit tests (41 tests covering detection and false positives) Co-Authored-By: Claude Opus 4.5 --- src/ai/researcher.ts | 6 +- src/utils/error-page.ts | 70 ++++++--- tests/unit/error-page.test.ts | 271 ++++++++++++++++++++++++++++++++++ 3 files changed, 320 insertions(+), 27 deletions(-) create mode 100644 tests/unit/error-page.test.ts diff --git a/src/ai/researcher.ts b/src/ai/researcher.ts index 0a54816..25950a6 100644 --- a/src/ai/researcher.ts +++ b/src/ai/researcher.ts @@ -109,13 +109,11 @@ export class Researcher implements Agent { const isOnCurrentState = this.actionResult!.getStateHash() === this.stateManager.getCurrentState()?.hash; await this.ensureNavigated(state.url, screenshot && this.provider.hasVision()); - const errorCheck = isErrorPage(this.actionResult!); - if (errorCheck.isError) { - tag('warn').log(`Detected ${errorCheck.type} page at ${state.url}`); + if (isErrorPage(this.actionResult!)) { + tag('warn').log(`Detected error page at ${state.url}`); return dedent` ## Error Page Detected - This appears to be a **${errorCheck.type}** page. URL: ${state.url} Title: ${this.actionResult!.title || 'N/A'} diff --git a/src/utils/error-page.ts b/src/utils/error-page.ts index f341426..96de015 100644 --- a/src/utils/error-page.ts +++ b/src/utils/error-page.ts @@ -1,40 +1,64 @@ import type { ActionResult } from '../action-result.js'; import { isBodyEmpty } from './html.js'; -export type ErrorPageResult = { - isError: boolean; - type?: '404' | '500' | '503' | '502' | '403' | 'empty'; -}; - -const ERROR_PATTERNS: { pattern: RegExp; type: '404' | '500' | '503' | '502' | '403' }[] = [ - { pattern: /\b404\b/, type: '404' }, - { pattern: /\b500\b/, type: '500' }, - { pattern: /\b503\b/, type: '503' }, - { pattern: /\b502\b/, type: '502' }, - { pattern: /\b403\b/, type: '403' }, +const ERROR_CODE_PATTERNS: RegExp[] = [ + /\b404\b.*?(error|not\s*found)/i, + /(error|not\s*found).*?\b404\b/i, + /^404$/i, + + /\b500\b.*?(error|server|internal)/i, + /(error|server|internal).*?\b500\b/i, + /^500$/i, + + /\b502\b.*?(error|gateway|bad)/i, + /(error|gateway|bad).*?\b502\b/i, + /^502$/i, + + /\b503\b.*?(error|service|unavailable)/i, + /(error|service|unavailable).*?\b503\b/i, + /^503$/i, + + /\b403\b.*?(error|forbidden|denied|access)/i, + /(error|forbidden|denied|access).*?\b403\b/i, + /^403$/i, +]; + +const ERROR_TEXT_PATTERNS: RegExp[] = [ + /\bpage\s*not\s*found\b/i, + /\bnot\s*found\b/i, + /\binternal\s*server\s*error\b/i, + /\bserver\s*error\b/i, + /\bservice\s*unavailable\b/i, + /\bbad\s*gateway\b/i, + /\baccess\s*denied\b/i, + /\bforbidden\b/i, + /\bsomething\s*went\s*wrong\b/i, + /\boops\b/i, + /\ban?\s*error\s*(has\s*)?(occurred|happened)\b/i, + /^error$/i, ]; const SMALL_PAGE_THRESHOLD = 500; -export function isErrorPage(actionResult: ActionResult): ErrorPageResult { - const checkFields = [actionResult.title, actionResult.h1, actionResult.h2].filter(Boolean); +export function isErrorPage(actionResult: ActionResult): boolean { + const checkFields = [actionResult.title, actionResult.h1, actionResult.h2].filter(Boolean) as string[]; for (const field of checkFields) { - for (const { pattern, type } of ERROR_PATTERNS) { - if (pattern.test(field!)) { - return { isError: true, type }; - } + for (const pattern of ERROR_CODE_PATTERNS) { + if (pattern.test(field)) return true; } } - if (!actionResult.html || isBodyEmpty(actionResult.html)) { - return { isError: true, type: 'empty' }; + for (const field of checkFields) { + for (const pattern of ERROR_TEXT_PATTERNS) { + if (pattern.test(field)) return true; + } } + if (!actionResult.html || isBodyEmpty(actionResult.html)) return true; + const bodyMatch = actionResult.html.match(/]*>([\s\S]*?)<\/body>/i); - if (bodyMatch && bodyMatch[1].trim().length < SMALL_PAGE_THRESHOLD) { - return { isError: true, type: 'empty' }; - } + if (bodyMatch && bodyMatch[1].trim().length < SMALL_PAGE_THRESHOLD) return true; - return { isError: false }; + return false; } diff --git a/tests/unit/error-page.test.ts b/tests/unit/error-page.test.ts new file mode 100644 index 0000000..90532d9 --- /dev/null +++ b/tests/unit/error-page.test.ts @@ -0,0 +1,271 @@ +import { describe, expect, it } from 'vitest'; +import { ActionResult } from '../../src/action-result.ts'; +import { isErrorPage } from '../../src/utils/error-page.ts'; + +function createActionResult(data: { title?: string; h1?: string; h2?: string; html?: string; url?: string }): ActionResult { + const html = data.html ?? `

${data.h1 ?? ''}

${data.h2 ?? ''}

`; + return new ActionResult({ + url: data.url ?? '/test', + title: data.title ?? '', + html, + }); +} + +describe('isErrorPage', () => { + describe('404 error detection', () => { + it('should detect 404 in title with error context', () => { + expect(isErrorPage(createActionResult({ title: '404 Not Found' }))).toBe(true); + }); + + it('should detect 404 in h1 with error context', () => { + expect(isErrorPage(createActionResult({ h1: '404 - Page Not Found' }))).toBe(true); + }); + + it('should detect 404 in h2 with error context', () => { + expect(isErrorPage(createActionResult({ h2: 'Error 404' }))).toBe(true); + }); + + it('should detect standalone 404', () => { + expect(isErrorPage(createActionResult({ title: '404' }))).toBe(true); + }); + + it('should detect "Page not found" without number', () => { + expect(isErrorPage(createActionResult({ h1: 'Page Not Found' }))).toBe(true); + }); + + it('should detect "Not Found" in title', () => { + expect(isErrorPage(createActionResult({ title: 'Not Found | MyApp' }))).toBe(true); + }); + }); + + describe('500 error detection', () => { + it('should detect 500 in title with error context', () => { + expect(isErrorPage(createActionResult({ title: '500 Internal Server Error' }))).toBe(true); + }); + + it('should detect 500 in h1 with error context', () => { + expect(isErrorPage(createActionResult({ h1: 'Error 500' }))).toBe(true); + }); + + it('should detect "Internal Server Error" text', () => { + expect(isErrorPage(createActionResult({ h1: 'Internal Server Error' }))).toBe(true); + }); + + it('should detect "Server Error" text', () => { + expect(isErrorPage(createActionResult({ title: 'Server Error' }))).toBe(true); + }); + }); + + describe('502/503 error detection', () => { + it('should detect 502 Bad Gateway', () => { + expect(isErrorPage(createActionResult({ title: '502 Bad Gateway' }))).toBe(true); + }); + + it('should detect 503 Service Unavailable', () => { + expect(isErrorPage(createActionResult({ title: '503 Service Unavailable' }))).toBe(true); + }); + + it('should detect "Service Unavailable" text', () => { + expect(isErrorPage(createActionResult({ h1: 'Service Unavailable' }))).toBe(true); + }); + + it('should detect "Bad Gateway" text', () => { + expect(isErrorPage(createActionResult({ h1: 'Bad Gateway' }))).toBe(true); + }); + }); + + describe('403 error detection', () => { + it('should detect 403 Forbidden', () => { + expect(isErrorPage(createActionResult({ title: '403 Forbidden' }))).toBe(true); + }); + + it('should detect "Access Denied" text', () => { + expect(isErrorPage(createActionResult({ h1: 'Access Denied' }))).toBe(true); + }); + + it('should detect "Forbidden" text', () => { + expect(isErrorPage(createActionResult({ title: 'Forbidden' }))).toBe(true); + }); + }); + + describe('generic error detection', () => { + it('should detect "Something went wrong"', () => { + expect(isErrorPage(createActionResult({ h1: 'Something Went Wrong' }))).toBe(true); + }); + + it('should detect "Oops!"', () => { + expect(isErrorPage(createActionResult({ h1: 'Oops! Something happened' }))).toBe(true); + }); + + it('should detect "Error" as standalone word in title', () => { + expect(isErrorPage(createActionResult({ title: 'Error' }))).toBe(true); + }); + + it('should detect "An error occurred"', () => { + expect(isErrorPage(createActionResult({ h1: 'An error occurred' }))).toBe(true); + }); + }); + + describe('empty page detection', () => { + it('should detect empty html', () => { + expect(isErrorPage(createActionResult({ html: '' }))).toBe(true); + }); + + it('should detect empty body', () => { + expect(isErrorPage(createActionResult({ html: '' }))).toBe(true); + }); + + it('should detect body with only whitespace', () => { + expect(isErrorPage(createActionResult({ html: ' \n\t ' }))).toBe(true); + }); + + it('should detect very small page (< 500 chars)', () => { + const smallContent = 'x'.repeat(100); + expect(isErrorPage(createActionResult({ html: `${smallContent}` }))).toBe(true); + }); + + it('should NOT detect page with 500+ chars as empty', () => { + const content = 'x'.repeat(600); + expect(isErrorPage(createActionResult({ html: `${content}` }))).toBe(false); + }); + }); + + describe('false positive prevention', () => { + it('should NOT detect "Room 404" as error page', () => { + const result = isErrorPage( + createActionResult({ + h1: 'Room 404', + html: '

Room 404

Hotel room.

' + 'x'.repeat(600) + '', + }) + ); + expect(result).toBe(false); + }); + + it('should NOT detect "Order #500" as error page', () => { + const result = isErrorPage( + createActionResult({ + title: 'Order #500 - Details', + html: '

Order Details

' + 'x'.repeat(600) + '', + }) + ); + expect(result).toBe(false); + }); + + it('should NOT detect "Product 403" as error page', () => { + const result = isErrorPage( + createActionResult({ + h1: 'Product 403', + html: '

Product 403

' + 'x'.repeat(600) + '', + }) + ); + expect(result).toBe(false); + }); + + it('should NOT detect "$500 price" as error page', () => { + const result = isErrorPage( + createActionResult({ + h1: '$500 Gift Card', + html: '

$500 Gift Card

' + 'x'.repeat(600) + '', + }) + ); + expect(result).toBe(false); + }); + + it('should NOT detect "500 items" as error page', () => { + const result = isErrorPage( + createActionResult({ + h1: '500 items in stock', + html: '

500 items in stock

' + 'x'.repeat(600) + '', + }) + ); + expect(result).toBe(false); + }); + + it('should NOT detect page number 404 as error', () => { + const result = isErrorPage( + createActionResult({ + title: 'Page 404 of 1000', + html: '

Results

' + 'x'.repeat(600) + '', + }) + ); + expect(result).toBe(false); + }); + + it('should NOT detect article/post ID 500 as error', () => { + const result = isErrorPage( + createActionResult({ + title: 'Post #500', + html: '

Blog Post

' + 'x'.repeat(600) + '', + }) + ); + expect(result).toBe(false); + }); + + it('should NOT detect "Newsletter" page as error', () => { + const result = isErrorPage( + createActionResult({ + h1: 'Newsletter', + html: '

Newsletter

' + 'x'.repeat(600) + '', + }) + ); + expect(result).toBe(false); + }); + + it('should NOT detect normal login page', () => { + const result = isErrorPage( + createActionResult({ + title: 'Login', + h1: 'Sign In', + html: '

Sign In

' + 'x'.repeat(600) + '
', + }) + ); + expect(result).toBe(false); + }); + + it('should NOT detect normal dashboard page', () => { + const result = isErrorPage( + createActionResult({ + title: 'Dashboard', + h1: 'Welcome Back', + html: '

Welcome Back

' + 'x'.repeat(600) + '', + }) + ); + expect(result).toBe(false); + }); + }); + + describe('edge cases', () => { + it('should handle missing title, h1, h2', () => { + const result = isErrorPage( + createActionResult({ + html: '

Content

' + 'x'.repeat(600) + '', + }) + ); + expect(result).toBe(false); + }); + + it('should handle null/undefined html gracefully', () => { + const actionResult = new ActionResult({ url: '/test', title: '' }); + expect(isErrorPage(actionResult)).toBe(true); + }); + + it('should detect error even with content if error text is clear', () => { + const result = isErrorPage( + createActionResult({ + title: '404 Not Found', + h1: 'Page Not Found', + html: '

Page Not Found

' + 'x'.repeat(600) + '', + }) + ); + expect(result).toBe(true); + }); + + it('should handle case insensitivity', () => { + expect(isErrorPage(createActionResult({ h1: 'PAGE NOT FOUND' }))).toBe(true); + }); + + it('should handle mixed case', () => { + expect(isErrorPage(createActionResult({ title: 'Page Not Found' }))).toBe(true); + }); + }); +}); From e68cb3f689f576638276cb91e8a783d719595cee Mon Sep 17 00:00:00 2001 From: DavertMik Date: Fri, 6 Feb 2026 06:07:05 +0200 Subject: [PATCH 3/3] Simplify error detection to exact HTTP error messages Just match standard HTTP error strings like "404 Not Found", "500 Internal Server Error" - no regex guessing. Co-Authored-By: Claude Opus 4.5 --- src/utils/error-page.ts | 47 +--------- tests/unit/error-page.test.ts | 162 ++++++---------------------------- 2 files changed, 28 insertions(+), 181 deletions(-) diff --git a/src/utils/error-page.ts b/src/utils/error-page.ts index 96de015..8b6bee2 100644 --- a/src/utils/error-page.ts +++ b/src/utils/error-page.ts @@ -1,42 +1,7 @@ import type { ActionResult } from '../action-result.js'; import { isBodyEmpty } from './html.js'; -const ERROR_CODE_PATTERNS: RegExp[] = [ - /\b404\b.*?(error|not\s*found)/i, - /(error|not\s*found).*?\b404\b/i, - /^404$/i, - - /\b500\b.*?(error|server|internal)/i, - /(error|server|internal).*?\b500\b/i, - /^500$/i, - - /\b502\b.*?(error|gateway|bad)/i, - /(error|gateway|bad).*?\b502\b/i, - /^502$/i, - - /\b503\b.*?(error|service|unavailable)/i, - /(error|service|unavailable).*?\b503\b/i, - /^503$/i, - - /\b403\b.*?(error|forbidden|denied|access)/i, - /(error|forbidden|denied|access).*?\b403\b/i, - /^403$/i, -]; - -const ERROR_TEXT_PATTERNS: RegExp[] = [ - /\bpage\s*not\s*found\b/i, - /\bnot\s*found\b/i, - /\binternal\s*server\s*error\b/i, - /\bserver\s*error\b/i, - /\bservice\s*unavailable\b/i, - /\bbad\s*gateway\b/i, - /\baccess\s*denied\b/i, - /\bforbidden\b/i, - /\bsomething\s*went\s*wrong\b/i, - /\boops\b/i, - /\ban?\s*error\s*(has\s*)?(occurred|happened)\b/i, - /^error$/i, -]; +const HTTP_ERRORS = ['400 Bad Request', '401 Unauthorized', '403 Forbidden', '404 Not Found', '405 Method Not Allowed', '408 Request Timeout', '500 Internal Server Error', '502 Bad Gateway', '503 Service Unavailable', '504 Gateway Timeout']; const SMALL_PAGE_THRESHOLD = 500; @@ -44,14 +9,8 @@ export function isErrorPage(actionResult: ActionResult): boolean { const checkFields = [actionResult.title, actionResult.h1, actionResult.h2].filter(Boolean) as string[]; for (const field of checkFields) { - for (const pattern of ERROR_CODE_PATTERNS) { - if (pattern.test(field)) return true; - } - } - - for (const field of checkFields) { - for (const pattern of ERROR_TEXT_PATTERNS) { - if (pattern.test(field)) return true; + for (const error of HTTP_ERRORS) { + if (field.toLowerCase().includes(error.toLowerCase())) return true; } } diff --git a/tests/unit/error-page.test.ts b/tests/unit/error-page.test.ts index 90532d9..bc67d1a 100644 --- a/tests/unit/error-page.test.ts +++ b/tests/unit/error-page.test.ts @@ -12,51 +12,35 @@ function createActionResult(data: { title?: string; h1?: string; h2?: string; ht } describe('isErrorPage', () => { - describe('404 error detection', () => { - it('should detect 404 in title with error context', () => { - expect(isErrorPage(createActionResult({ title: '404 Not Found' }))).toBe(true); + describe('HTTP error detection', () => { + it('should detect 400 Bad Request', () => { + expect(isErrorPage(createActionResult({ title: '400 Bad Request' }))).toBe(true); }); - it('should detect 404 in h1 with error context', () => { - expect(isErrorPage(createActionResult({ h1: '404 - Page Not Found' }))).toBe(true); + it('should detect 401 Unauthorized', () => { + expect(isErrorPage(createActionResult({ title: '401 Unauthorized' }))).toBe(true); }); - it('should detect 404 in h2 with error context', () => { - expect(isErrorPage(createActionResult({ h2: 'Error 404' }))).toBe(true); + it('should detect 403 Forbidden', () => { + expect(isErrorPage(createActionResult({ title: '403 Forbidden' }))).toBe(true); }); - it('should detect standalone 404', () => { - expect(isErrorPage(createActionResult({ title: '404' }))).toBe(true); + it('should detect 404 Not Found', () => { + expect(isErrorPage(createActionResult({ title: '404 Not Found' }))).toBe(true); }); - it('should detect "Page not found" without number', () => { - expect(isErrorPage(createActionResult({ h1: 'Page Not Found' }))).toBe(true); + it('should detect 404 Not Found in h1', () => { + expect(isErrorPage(createActionResult({ h1: '404 Not Found' }))).toBe(true); }); - it('should detect "Not Found" in title', () => { - expect(isErrorPage(createActionResult({ title: 'Not Found | MyApp' }))).toBe(true); + it('should detect 404 Not Found in h2', () => { + expect(isErrorPage(createActionResult({ h2: '404 Not Found' }))).toBe(true); }); - }); - describe('500 error detection', () => { - it('should detect 500 in title with error context', () => { + it('should detect 500 Internal Server Error', () => { expect(isErrorPage(createActionResult({ title: '500 Internal Server Error' }))).toBe(true); }); - it('should detect 500 in h1 with error context', () => { - expect(isErrorPage(createActionResult({ h1: 'Error 500' }))).toBe(true); - }); - - it('should detect "Internal Server Error" text', () => { - expect(isErrorPage(createActionResult({ h1: 'Internal Server Error' }))).toBe(true); - }); - - it('should detect "Server Error" text', () => { - expect(isErrorPage(createActionResult({ title: 'Server Error' }))).toBe(true); - }); - }); - - describe('502/503 error detection', () => { it('should detect 502 Bad Gateway', () => { expect(isErrorPage(createActionResult({ title: '502 Bad Gateway' }))).toBe(true); }); @@ -65,44 +49,17 @@ describe('isErrorPage', () => { expect(isErrorPage(createActionResult({ title: '503 Service Unavailable' }))).toBe(true); }); - it('should detect "Service Unavailable" text', () => { - expect(isErrorPage(createActionResult({ h1: 'Service Unavailable' }))).toBe(true); - }); - - it('should detect "Bad Gateway" text', () => { - expect(isErrorPage(createActionResult({ h1: 'Bad Gateway' }))).toBe(true); - }); - }); - - describe('403 error detection', () => { - it('should detect 403 Forbidden', () => { - expect(isErrorPage(createActionResult({ title: '403 Forbidden' }))).toBe(true); + it('should detect 504 Gateway Timeout', () => { + expect(isErrorPage(createActionResult({ title: '504 Gateway Timeout' }))).toBe(true); }); - it('should detect "Access Denied" text', () => { - expect(isErrorPage(createActionResult({ h1: 'Access Denied' }))).toBe(true); + it('should be case insensitive', () => { + expect(isErrorPage(createActionResult({ title: '404 NOT FOUND' }))).toBe(true); + expect(isErrorPage(createActionResult({ title: '500 internal server error' }))).toBe(true); }); - it('should detect "Forbidden" text', () => { - expect(isErrorPage(createActionResult({ title: 'Forbidden' }))).toBe(true); - }); - }); - - describe('generic error detection', () => { - it('should detect "Something went wrong"', () => { - expect(isErrorPage(createActionResult({ h1: 'Something Went Wrong' }))).toBe(true); - }); - - it('should detect "Oops!"', () => { - expect(isErrorPage(createActionResult({ h1: 'Oops! Something happened' }))).toBe(true); - }); - - it('should detect "Error" as standalone word in title', () => { - expect(isErrorPage(createActionResult({ title: 'Error' }))).toBe(true); - }); - - it('should detect "An error occurred"', () => { - expect(isErrorPage(createActionResult({ h1: 'An error occurred' }))).toBe(true); + it('should detect error in longer title', () => { + expect(isErrorPage(createActionResult({ title: 'MyApp - 404 Not Found' }))).toBe(true); }); }); @@ -135,7 +92,7 @@ describe('isErrorPage', () => { const result = isErrorPage( createActionResult({ h1: 'Room 404', - html: '

Room 404

Hotel room.

' + 'x'.repeat(600) + '', + html: '

Room 404

' + 'x'.repeat(600) + '', }) ); expect(result).toBe(false); @@ -151,61 +108,11 @@ describe('isErrorPage', () => { expect(result).toBe(false); }); - it('should NOT detect "Product 403" as error page', () => { + it('should NOT detect standalone 404 number', () => { const result = isErrorPage( createActionResult({ - h1: 'Product 403', - html: '

Product 403

' + 'x'.repeat(600) + '', - }) - ); - expect(result).toBe(false); - }); - - it('should NOT detect "$500 price" as error page', () => { - const result = isErrorPage( - createActionResult({ - h1: '$500 Gift Card', - html: '

$500 Gift Card

' + 'x'.repeat(600) + '', - }) - ); - expect(result).toBe(false); - }); - - it('should NOT detect "500 items" as error page', () => { - const result = isErrorPage( - createActionResult({ - h1: '500 items in stock', - html: '

500 items in stock

' + 'x'.repeat(600) + '', - }) - ); - expect(result).toBe(false); - }); - - it('should NOT detect page number 404 as error', () => { - const result = isErrorPage( - createActionResult({ - title: 'Page 404 of 1000', - html: '

Results

' + 'x'.repeat(600) + '', - }) - ); - expect(result).toBe(false); - }); - - it('should NOT detect article/post ID 500 as error', () => { - const result = isErrorPage( - createActionResult({ - title: 'Post #500', - html: '

Blog Post

' + 'x'.repeat(600) + '', - }) - ); - expect(result).toBe(false); - }); - - it('should NOT detect "Newsletter" page as error', () => { - const result = isErrorPage( - createActionResult({ - h1: 'Newsletter', - html: '

Newsletter

' + 'x'.repeat(600) + '', + title: '404', + html: '' + 'x'.repeat(600) + '', }) ); expect(result).toBe(false); @@ -248,24 +155,5 @@ describe('isErrorPage', () => { const actionResult = new ActionResult({ url: '/test', title: '' }); expect(isErrorPage(actionResult)).toBe(true); }); - - it('should detect error even with content if error text is clear', () => { - const result = isErrorPage( - createActionResult({ - title: '404 Not Found', - h1: 'Page Not Found', - html: '

Page Not Found

' + 'x'.repeat(600) + '', - }) - ); - expect(result).toBe(true); - }); - - it('should handle case insensitivity', () => { - expect(isErrorPage(createActionResult({ h1: 'PAGE NOT FOUND' }))).toBe(true); - }); - - it('should handle mixed case', () => { - expect(isErrorPage(createActionResult({ title: 'Page Not Found' }))).toBe(true); - }); }); });