crawailer/tests/test_platform_edge_cases.py
Crawailer Developer d31395a166 Initial Crawailer implementation with comprehensive JavaScript API
- Complete browser automation with Playwright integration
- High-level API functions: get(), get_many(), discover()
- JavaScript execution support with script parameters
- Content extraction optimized for LLM workflows
- Comprehensive test suite with 18 test files (700+ scenarios)
- Local Caddy test server for reproducible testing
- Performance benchmarking vs Katana crawler
- Complete documentation including JavaScript API guide
- PyPI-ready packaging with professional metadata
- UNIX philosophy: do web scraping exceptionally well
2025-09-18 14:47:59 -06:00

1404 lines
66 KiB
Python

"""
Platform-specific edge case test suite.
Tests JavaScript execution across different operating systems, browser engines,
hardware configurations, and edge cases specific to Linux, Windows, macOS,
mobile platforms, and embedded environments.
"""
import pytest
import asyncio
from typing import Dict, Any, List, Optional, Tuple
from unittest.mock import AsyncMock, MagicMock, patch
import json
import platform
import sys
from crawailer import get, get_many
from crawailer.browser import Browser
from crawailer.config import BrowserConfig
class TestPlatformEdgeCases:
"""Test platform-specific edge cases and browser behavior differences."""
@pytest.fixture
def base_url(self):
"""Base URL for local test server."""
return "http://localhost:8083"
@pytest.fixture
def platform_info(self):
"""Current platform information."""
return {
'system': platform.system(),
'release': platform.release(),
'version': platform.version(),
'machine': platform.machine(),
'processor': platform.processor(),
'python_version': sys.version,
'architecture': platform.architecture()
}
# Operating System Specific Tests
@pytest.mark.asyncio
async def test_linux_specific_behaviors(self, base_url, platform_info):
"""Test Linux-specific browser behaviors and edge cases."""
content = await get(
f"{base_url}/react/",
script="""
// Detect Linux-specific features and behaviors
const linuxFeatures = {
platform: navigator.platform,
userAgent: navigator.userAgent,
isLinux: navigator.platform.includes('Linux') || navigator.userAgent.includes('Linux'),
// Linux-specific capabilities
hasWayland: false, // Can't directly detect, but we can test rendering
hasX11: false, // Can't directly detect
// Font rendering and display
fontRendering: {
hasSubpixelAntialiasing: CSS.supports('font-smooth', 'subpixel-antialiased'),
hasSystemFonts: CSS.supports('font-family', 'system-ui'),
canvasTextRendering: null
},
// Hardware acceleration
hardwareAcceleration: {
hasWebGL: !!window.WebGLRenderingContext,
webglRenderer: null,
webglVendor: null,
canvas2dAccelerated: null
},
// Memory and performance characteristics
memoryInfo: performance.memory ? {
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
totalJSHeapSize: performance.memory.totalJSHeapSize,
usedJSHeapSize: performance.memory.usedJSHeapSize
} : null,
// File system behaviors
fileSystem: {
caseSensitive: null, // Will test below
pathSeparator: '/', // Linux default
homeDirectory: null, // Can't access directly from browser
supportsSymlinks: null // Can't test directly
}
};
// Test WebGL capabilities
if (linuxFeatures.hardwareAcceleration.hasWebGL) {
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (gl) {
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
linuxFeatures.hardwareAcceleration.webglRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
linuxFeatures.hardwareAcceleration.webglVendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
}
}
} catch (error) {
linuxFeatures.hardwareAcceleration.webglError = error.message;
}
}
// Test canvas text rendering quality
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 200;
canvas.height = 50;
// Test different text rendering modes
ctx.font = '16px Arial';
ctx.textBaseline = 'top';
// Default rendering
ctx.fillText('Test Text Default', 10, 10);
// With text rendering optimizations
ctx.textRenderingOptimization = 'optimizeLegibility';
ctx.fillText('Test Text Optimized', 10, 30);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const hasRenderedContent = Array.from(imageData.data).some(pixel => pixel !== 0);
linuxFeatures.fontRendering.canvasTextRendering = {
success: hasRenderedContent,
canvasSupported: true
};
} catch (error) {
linuxFeatures.fontRendering.canvasTextRendering = {
success: false,
error: error.message
};
}
// Test file case sensitivity (limited browser test)
try {
// This is a very indirect test - testing URL case sensitivity
const testUrl1 = '/API/test';
const testUrl2 = '/api/test';
// Note: This doesn't actually test filesystem, just URL handling
linuxFeatures.fileSystem.urlCaseSensitive = testUrl1 !== testUrl2;
} catch (error) {
linuxFeatures.fileSystem.caseSensitivityTestError = error.message;
}
// Test Linux-specific performance characteristics
const performanceTest = {
startTime: performance.now(),
iterations: 10000,
results: []
};
// CPU-intensive operation
for (let i = 0; i < performanceTest.iterations; i++) {
Math.sqrt(i);
}
performanceTest.endTime = performance.now();
performanceTest.duration = performanceTest.endTime - performanceTest.startTime;
performanceTest.operationsPerSecond = performanceTest.iterations / (performanceTest.duration / 1000);
linuxFeatures.performanceCharacteristics = performanceTest;
// Test process and thread information (limited in browser)
linuxFeatures.processInfo = {
hardwareConcurrency: navigator.hardwareConcurrency,
maxTouchPoints: navigator.maxTouchPoints,
cookieEnabled: navigator.cookieEnabled,
onLine: navigator.onLine
};
return linuxFeatures;
"""
)
assert content.script_result is not None
result = content.script_result
# Verify Linux detection and features
if platform_info['system'] == 'Linux':
assert result['isLinux'] is True
assert 'Linux' in result['userAgent'] or 'Linux' in result['platform']
# Check hardware acceleration
hw_accel = result['hardwareAcceleration']
assert 'hasWebGL' in hw_accel
# Check font rendering
font_rendering = result['fontRendering']
assert 'canvasTextRendering' in font_rendering
# Check performance characteristics
perf_test = result['performanceCharacteristics']
assert perf_test['duration'] > 0
assert perf_test['operationsPerSecond'] > 0
# Check process info
process_info = result['processInfo']
assert 'hardwareConcurrency' in process_info
assert process_info['hardwareConcurrency'] >= 1
@pytest.mark.asyncio
async def test_browser_engine_differences(self, base_url):
"""Test differences between browser engines (Chromium, Firefox, Safari)."""
content = await get(
f"{base_url}/vue/",
script="""
// Detect browser engine and test engine-specific behaviors
const engineDetection = {
userAgent: navigator.userAgent,
vendor: navigator.vendor,
// Engine detection
isChromium: !!window.chrome || navigator.userAgent.includes('Chrome'),
isGecko: navigator.userAgent.includes('Gecko') && !navigator.userAgent.includes('Chrome'),
isWebKit: navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome'),
isBlink: !!window.chrome, // Chromium uses Blink engine
// Engine-specific features
engineFeatures: {},
// API availability differences
apiSupport: {
webkitFeatures: {},
mozFeatures: {},
chromiumFeatures: {}
},
// Performance characteristics by engine
performanceTests: {},
// Rendering differences
renderingTests: {}
};
// Test Chromium/Blink specific features
if (engineDetection.isChromium) {
engineDetection.engineFeatures.chromium = {
hasChrome: !!window.chrome,
hasChromeRuntime: !!(window.chrome && window.chrome.runtime),
hasWebkitRequestFileSystem: !!window.webkitRequestFileSystem,
hasWebkitStorageInfo: !!(navigator.webkitTemporaryStorage || navigator.webkitPersistentStorage),
chromeVersion: navigator.userAgent.match(/Chrome\/([\\d.]+)/)?.[1] || 'unknown'
};
// Test Chromium-specific APIs
engineDetection.apiSupport.chromiumFeatures = {
fileSystemAccess: !!window.showOpenFilePicker,
webShare: !!navigator.share,
webSerial: !!navigator.serial,
webUSB: !!navigator.usb,
webBluetooth: !!navigator.bluetooth,
webNFC: !!window.NDEFReader,
webLocks: !!navigator.locks,
broadcastChannel: !!window.BroadcastChannel,
intersectionObserver: !!window.IntersectionObserver,
resizeObserver: !!window.ResizeObserver
};
}
// Test Gecko/Firefox specific features
if (engineDetection.isGecko) {
engineDetection.engineFeatures.gecko = {
hasMoz: typeof navigator.mozGetUserMedia !== 'undefined',
firefoxVersion: navigator.userAgent.match(/Firefox\/([\\d.]+)/)?.[1] || 'unknown',
mozInnerScreenX: typeof window.mozInnerScreenX !== 'undefined',
mozPaintCount: typeof window.mozPaintCount !== 'undefined'
};
// Test Firefox-specific APIs
engineDetection.apiSupport.mozFeatures = {
mozGetUserMedia: !!navigator.mozGetUserMedia,
mozRequestFullScreen: !!document.documentElement.mozRequestFullScreen,
mozIndexedDB: !!window.mozIndexedDB,
mozRTCPeerConnection: !!window.mozRTCPeerConnection
};
}
// Test WebKit/Safari specific features
if (engineDetection.isWebKit) {
engineDetection.engineFeatures.webkit = {
hasWebkit: navigator.userAgent.includes('WebKit'),
safariVersion: navigator.userAgent.match(/Version\/([\\d.]+)/)?.[1] || 'unknown',
webkitAppearance: CSS.supports('-webkit-appearance', 'none'),
webkitOverflowScrolling: CSS.supports('-webkit-overflow-scrolling', 'touch')
};
// Test WebKit-specific APIs
engineDetection.apiSupport.webkitFeatures = {
webkitRequestFileSystem: !!window.webkitRequestFileSystem,
webkitSpeechRecognition: !!window.webkitSpeechRecognition,
webkitAudioContext: !!window.webkitAudioContext,
webkitGetUserMedia: !!navigator.webkitGetUserMedia,
webkitRequestAnimationFrame: !!window.webkitRequestAnimationFrame
};
}
// Test performance differences between engines
const performanceTests = {
domManipulation: await testDOMPerformance(),
jsExecution: await testJSPerformance(),
canvasRendering: await testCanvasPerformance(),
memoryUsage: testMemoryUsage()
};
async function testDOMPerformance() {
const start = performance.now();
// Create and manipulate DOM elements
const container = document.createElement('div');
for (let i = 0; i < 1000; i++) {
const element = document.createElement('div');
element.textContent = `Element ${i}`;
element.className = 'test-element';
container.appendChild(element);
}
document.body.appendChild(container);
// Query and modify elements
const elements = container.querySelectorAll('.test-element');
elements.forEach((el, index) => {
if (index % 2 === 0) {
el.style.backgroundColor = 'lightblue';
}
});
const end = performance.now();
// Cleanup
document.body.removeChild(container);
return {
duration: end - start,
elementsCreated: 1000,
elementsPerSecond: 1000 / ((end - start) / 1000)
};
}
async function testJSPerformance() {
const start = performance.now();
// CPU-intensive JavaScript operations
let result = 0;
for (let i = 0; i < 100000; i++) {
result += Math.sqrt(i) * Math.sin(i);
}
const end = performance.now();
return {
duration: end - start,
operations: 100000,
operationsPerSecond: 100000 / ((end - start) / 1000),
result: result
};
}
async function testCanvasPerformance() {
const start = performance.now();
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 500;
const ctx = canvas.getContext('2d');
// Draw performance test
for (let i = 0; i < 1000; i++) {
ctx.beginPath();
ctx.arc(Math.random() * 500, Math.random() * 500, 10, 0, 2 * Math.PI);
ctx.fillStyle = `hsl(${i % 360}, 50%, 50%)`;
ctx.fill();
}
const end = performance.now();
return {
duration: end - start,
shapesDrawn: 1000,
shapesPerSecond: 1000 / ((end - start) / 1000),
canvasSize: '500x500'
};
}
function testMemoryUsage() {
if (performance.memory) {
return {
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
totalJSHeapSize: performance.memory.totalJSHeapSize,
usedJSHeapSize: performance.memory.usedJSHeapSize,
memoryPressure: performance.memory.usedJSHeapSize / performance.memory.jsHeapSizeLimit
};
}
return { available: false };
}
engineDetection.performanceTests = performanceTests;
// Test CSS rendering differences
const cssTests = {
flexboxSupport: CSS.supports('display', 'flex'),
gridSupport: CSS.supports('display', 'grid'),
customPropertiesSupport: CSS.supports('color', 'var(--test)'),
scrollSnapSupport: CSS.supports('scroll-snap-type', 'x mandatory'),
backdropFilterSupport: CSS.supports('backdrop-filter', 'blur(10px)'),
containerQueriesSupport: CSS.supports('container-type', 'inline-size')
};
engineDetection.cssSupport = cssTests;
return engineDetection;
"""
)
assert content.script_result is not None
result = content.script_result
# Verify engine detection
engines = ['isChromium', 'isGecko', 'isWebKit', 'isBlink']
detected_engines = [engine for engine in engines if result.get(engine)]
assert len(detected_engines) >= 1 # At least one engine should be detected
# Check API support
api_support = result['apiSupport']
assert 'chromiumFeatures' in api_support
assert 'mozFeatures' in api_support
assert 'webkitFeatures' in api_support
# Check performance tests
perf_tests = result['performanceTests']
assert 'domManipulation' in perf_tests
assert 'jsExecution' in perf_tests
assert 'canvasRendering' in perf_tests
# Verify DOM performance
dom_perf = perf_tests['domManipulation']
assert dom_perf['duration'] > 0
assert dom_perf['elementsCreated'] == 1000
assert dom_perf['elementsPerSecond'] > 0
# Verify JS performance
js_perf = perf_tests['jsExecution']
assert js_perf['duration'] > 0
assert js_perf['operations'] == 100000
assert js_perf['operationsPerSecond'] > 0
# Verify canvas performance
canvas_perf = perf_tests['canvasRendering']
assert canvas_perf['duration'] > 0
assert canvas_perf['shapesDrawn'] == 1000
assert canvas_perf['shapesPerSecond'] > 0
# Check CSS support
css_support = result['cssSupport']
assert css_support['flexboxSupport'] is True # Modern browsers should support flexbox
assert css_support['gridSupport'] is True # Modern browsers should support grid
@pytest.mark.asyncio
async def test_hardware_acceleration_edge_cases(self, base_url):
"""Test hardware acceleration edge cases and GPU-related issues."""
content = await get(
f"{base_url}/angular/",
script="""
// Test hardware acceleration and GPU-related edge cases
const hardwareTests = {
webglSupport: {
webgl1: false,
webgl2: false,
extensions: [],
rendererInfo: null,
limits: {}
},
canvasAcceleration: {
canvas2d: null,
offscreenCanvas: !!window.OffscreenCanvas,
transferableObjects: false
},
gpuInfo: {
vendor: null,
renderer: null,
maxTextureSize: null,
maxViewportDims: null
},
performanceTests: {
hardwareAccelerated: null,
softwareRendered: null,
comparison: null
},
memoryTests: {
textureMemory: null,
bufferMemory: null,
vertexArrays: null
}
};
// Test WebGL 1.0 support
try {
const canvas1 = document.createElement('canvas');
const gl1 = canvas1.getContext('webgl') || canvas1.getContext('experimental-webgl');
if (gl1) {
hardwareTests.webglSupport.webgl1 = true;
// Get available extensions
hardwareTests.webglSupport.extensions = gl1.getSupportedExtensions() || [];
// Get renderer info if available
const debugInfo = gl1.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
hardwareTests.gpuInfo.vendor = gl1.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
hardwareTests.gpuInfo.renderer = gl1.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
}
// Get WebGL limits
hardwareTests.webglSupport.limits = {
maxTextureSize: gl1.getParameter(gl1.MAX_TEXTURE_SIZE),
maxViewportDims: gl1.getParameter(gl1.MAX_VIEWPORT_DIMS),
maxVertexAttribs: gl1.getParameter(gl1.MAX_VERTEX_ATTRIBS),
maxTextureImageUnits: gl1.getParameter(gl1.MAX_TEXTURE_IMAGE_UNITS),
maxFragmentUniformVectors: gl1.getParameter(gl1.MAX_FRAGMENT_UNIFORM_VECTORS),
maxVertexUniformVectors: gl1.getParameter(gl1.MAX_VERTEX_UNIFORM_VECTORS)
};
// Store GPU info
hardwareTests.gpuInfo.maxTextureSize = hardwareTests.webglSupport.limits.maxTextureSize;
hardwareTests.gpuInfo.maxViewportDims = hardwareTests.webglSupport.limits.maxViewportDims;
// Test texture creation and memory usage
const testTextureMemory = () => {
try {
const texture = gl1.createTexture();
gl1.bindTexture(gl1.TEXTURE_2D, texture);
// Create a moderately sized texture to test memory
const size = Math.min(1024, hardwareTests.webglSupport.limits.maxTextureSize / 4);
const data = new Uint8Array(size * size * 4); // RGBA
gl1.texImage2D(gl1.TEXTURE_2D, 0, gl1.RGBA, size, size, 0, gl1.RGBA, gl1.UNSIGNED_BYTE, data);
const error = gl1.getError();
gl1.deleteTexture(texture);
return {
success: error === gl1.NO_ERROR,
textureSize: size,
memoryUsed: size * size * 4,
error: error !== gl1.NO_ERROR ? error : null
};
} catch (e) {
return { success: false, error: e.message };
}
};
hardwareTests.memoryTests.textureMemory = testTextureMemory();
// Test buffer creation
const testBufferMemory = () => {
try {
const buffer = gl1.createBuffer();
gl1.bindBuffer(gl1.ARRAY_BUFFER, buffer);
const bufferData = new Float32Array(10000); // 40KB buffer
gl1.bufferData(gl1.ARRAY_BUFFER, bufferData, gl1.STATIC_DRAW);
const error = gl1.getError();
gl1.deleteBuffer(buffer);
return {
success: error === gl1.NO_ERROR,
bufferSize: bufferData.byteLength,
error: error !== gl1.NO_ERROR ? error : null
};
} catch (e) {
return { success: false, error: e.message };
}
};
hardwareTests.memoryTests.bufferMemory = testBufferMemory();
}
} catch (error) {
hardwareTests.webglSupport.webgl1Error = error.message;
}
// Test WebGL 2.0 support
try {
const canvas2 = document.createElement('canvas');
const gl2 = canvas2.getContext('webgl2');
if (gl2) {
hardwareTests.webglSupport.webgl2 = true;
// Test WebGL 2.0 specific features
hardwareTests.webglSupport.webgl2Features = {
maxDrawBuffers: gl2.getParameter(gl2.MAX_DRAW_BUFFERS),
maxColorAttachments: gl2.getParameter(gl2.MAX_COLOR_ATTACHMENTS),
maxSamples: gl2.getParameter(gl2.MAX_SAMPLES),
maxTransformFeedbackInterleavedComponents: gl2.getParameter(gl2.MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS)
};
// Test vertex array objects
const testVertexArrays = () => {
try {
const vao = gl2.createVertexArray();
gl2.bindVertexArray(vao);
const buffer = gl2.createBuffer();
gl2.bindBuffer(gl2.ARRAY_BUFFER, buffer);
const vertices = new Float32Array([0, 0, 1, 0, 0.5, 1]);
gl2.bufferData(gl2.ARRAY_BUFFER, vertices, gl2.STATIC_DRAW);
gl2.enableVertexAttribArray(0);
gl2.vertexAttribPointer(0, 2, gl2.FLOAT, false, 0, 0);
gl2.bindVertexArray(null);
gl2.deleteVertexArray(vao);
gl2.deleteBuffer(buffer);
const error = gl2.getError();
return {
success: error === gl2.NO_ERROR,
error: error !== gl2.NO_ERROR ? error : null
};
} catch (e) {
return { success: false, error: e.message };
}
};
hardwareTests.memoryTests.vertexArrays = testVertexArrays();
}
} catch (error) {
hardwareTests.webglSupport.webgl2Error = error.message;
}
// Test Canvas 2D hardware acceleration
const testCanvas2DAcceleration = () => {
try {
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 500;
const ctx = canvas.getContext('2d');
// Test if context is hardware accelerated (indirect test)
const start = performance.now();
// Draw complex graphics that benefit from hardware acceleration
for (let i = 0; i < 1000; i++) {
ctx.save();
ctx.translate(250, 250);
ctx.rotate(i * 0.1);
ctx.scale(1 + Math.sin(i * 0.1) * 0.1, 1 + Math.cos(i * 0.1) * 0.1);
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, 50);
gradient.addColorStop(0, `hsl(${i % 360}, 70%, 50%)`);
gradient.addColorStop(1, `hsl(${(i + 180) % 360}, 70%, 30%)`);
ctx.fillStyle = gradient;
ctx.fillRect(-25, -25, 50, 50);
ctx.restore();
}
const end = performance.now();
return {
renderTime: end - start,
operationsPerSecond: 1000 / ((end - start) / 1000),
likelyHardwareAccelerated: (end - start) < 100 // Heuristic
};
} catch (error) {
return { success: false, error: error.message };
}
};
hardwareTests.canvasAcceleration.canvas2d = testCanvas2DAcceleration();
// Test OffscreenCanvas and transferable objects
if (hardwareTests.canvasAcceleration.offscreenCanvas) {
try {
const offscreen = new OffscreenCanvas(256, 256);
const ctx = offscreen.getContext('2d');
if (ctx) {
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 256, 256);
hardwareTests.canvasAcceleration.transferableObjects = typeof offscreen.transferControlToOffscreen === 'function';
}
} catch (error) {
hardwareTests.canvasAcceleration.offscreenError = error.message;
}
}
// Performance comparison test
const performanceComparison = async () => {
const results = {
webglTest: null,
canvas2dTest: null,
comparison: null
};
// WebGL performance test
if (hardwareTests.webglSupport.webgl1) {
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 500;
const gl = canvas.getContext('webgl');
if (gl) {
const start = performance.now();
// Simple WebGL rendering test
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Create and use a simple shader program would go here
// For this test, we'll just do multiple clear operations
for (let i = 0; i < 1000; i++) {
gl.clearColor(Math.random(), Math.random(), Math.random(), 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
}
const end = performance.now();
results.webglTest = {
duration: end - start,
operationsPerSecond: 1000 / ((end - start) / 1000)
};
}
}
// Canvas 2D performance test
const canvas2d = document.createElement('canvas');
canvas2d.width = 500;
canvas2d.height = 500;
const ctx2d = canvas2d.getContext('2d');
const start2d = performance.now();
for (let i = 0; i < 1000; i++) {
ctx2d.fillStyle = `hsl(${i % 360}, 50%, 50%)`;
ctx2d.fillRect(0, 0, 500, 500);
}
const end2d = performance.now();
results.canvas2dTest = {
duration: end2d - start2d,
operationsPerSecond: 1000 / ((end2d - start2d) / 1000)
};
// Compare performance
if (results.webglTest && results.canvas2dTest) {
results.comparison = {
webglFaster: results.webglTest.operationsPerSecond > results.canvas2dTest.operationsPerSecond,
speedRatio: results.webglTest.operationsPerSecond / results.canvas2dTest.operationsPerSecond,
webglAdvantage: results.webglTest.operationsPerSecond - results.canvas2dTest.operationsPerSecond
};
}
return results;
};
hardwareTests.performanceTests = await performanceComparison();
return hardwareTests;
"""
)
assert content.script_result is not None
result = content.script_result
# Verify WebGL support testing
webgl_support = result['webglSupport']
assert 'webgl1' in webgl_support
assert 'webgl2' in webgl_support
if webgl_support['webgl1']:
assert 'limits' in webgl_support
limits = webgl_support['limits']
assert 'maxTextureSize' in limits
assert limits['maxTextureSize'] > 0
# Check memory tests
memory_tests = result['memoryTests']
if memory_tests['textureMemory']:
assert 'success' in memory_tests['textureMemory']
if memory_tests['bufferMemory']:
assert 'success' in memory_tests['bufferMemory']
# Verify Canvas 2D acceleration testing
canvas_accel = result['canvasAcceleration']
assert 'canvas2d' in canvas_accel
if canvas_accel['canvas2d'] and 'renderTime' in canvas_accel['canvas2d']:
canvas2d_test = canvas_accel['canvas2d']
assert canvas2d_test['renderTime'] > 0
assert canvas2d_test['operationsPerSecond'] > 0
# Check performance tests
perf_tests = result['performanceTests']
if perf_tests and perf_tests['canvas2dTest']:
canvas2d_perf = perf_tests['canvas2dTest']
assert canvas2d_perf['duration'] > 0
assert canvas2d_perf['operationsPerSecond'] > 0
# Memory and Resource Management Edge Cases
@pytest.mark.asyncio
async def test_memory_pressure_scenarios(self, base_url):
"""Test behavior under various memory pressure scenarios."""
content = await get(
f"{base_url}/react/",
script="""
// Test memory pressure scenarios and garbage collection
class MemoryPressureTester {
constructor() {
this.memorySnapshots = [];
this.testResults = {};
this.originalMemory = this.getMemoryInfo();
}
getMemoryInfo() {
if (performance.memory) {
return {
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
totalJSHeapSize: performance.memory.totalJSHeapSize,
usedJSHeapSize: performance.memory.usedJSHeapSize,
timestamp: Date.now()
};
}
return {
jsHeapSizeLimit: null,
totalJSHeapSize: null,
usedJSHeapSize: null,
timestamp: Date.now(),
unavailable: true
};
}
takeMemorySnapshot(label) {
const snapshot = {
label,
...this.getMemoryInfo()
};
this.memorySnapshots.push(snapshot);
return snapshot;
}
async testGradualMemoryIncrease() {
const testData = [];
const phases = [
{ name: 'baseline', allocations: 0 },
{ name: 'small_load', allocations: 1000 },
{ name: 'medium_load', allocations: 10000 },
{ name: 'large_load', allocations: 100000 }
];
for (const phase of phases) {
this.takeMemorySnapshot(`start_${phase.name}`);
// Allocate memory gradually
const allocatedData = [];
for (let i = 0; i < phase.allocations; i++) {
allocatedData.push({
id: i,
data: new Array(100).fill(Math.random()),
metadata: {
created: Date.now(),
type: 'test_data',
phase: phase.name
}
});
// Yield occasionally to prevent blocking
if (i % 1000 === 0) {
await new Promise(resolve => setTimeout(resolve, 1));
}
}
const endSnapshot = this.takeMemorySnapshot(`end_${phase.name}`);
testData.push({
phase: phase.name,
allocations: phase.allocations,
dataSize: allocatedData.length,
memorySnapshot: endSnapshot
});
// Clean up some data but not all (simulate memory leaks)
if (Math.random() > 0.3) { // 70% chance to clean up
allocatedData.length = Math.floor(allocatedData.length * 0.7);
}
// Wait a bit between phases
await new Promise(resolve => setTimeout(resolve, 100));
}
return testData;
}
async testMemoryFragmentation() {
this.takeMemorySnapshot('fragmentation_start');
const fragmentationTest = {
allocations: [],
deallocations: 0,
reallocations: 0
};
// Create lots of small allocations
for (let i = 0; i < 5000; i++) {
const allocation = {
id: i,
smallData: new Array(Math.floor(Math.random() * 50) + 10).fill(i),
timestamp: Date.now()
};
fragmentationTest.allocations.push(allocation);
}
this.takeMemorySnapshot('fragmentation_allocated');
// Randomly deallocate some objects (simulate fragmentation)
for (let i = 0; i < 2000; i++) {
const randomIndex = Math.floor(Math.random() * fragmentationTest.allocations.length);
if (fragmentationTest.allocations[randomIndex]) {
delete fragmentationTest.allocations[randomIndex];
fragmentationTest.deallocations++;
}
}
this.takeMemorySnapshot('fragmentation_deallocated');
// Reallocate in the gaps
for (let i = 0; i < 1000; i++) {
const allocation = {
id: 'realloc_' + i,
reallocData: new Array(Math.floor(Math.random() * 100) + 20).fill('realloc'),
timestamp: Date.now()
};
fragmentationTest.allocations.push(allocation);
fragmentationTest.reallocations++;
}
this.takeMemorySnapshot('fragmentation_reallocated');
return fragmentationTest;
}
async testMemoryLeakSimulation() {
this.takeMemorySnapshot('leak_test_start');
const leakTest = {
intentionalLeaks: [],
eventListeners: [],
intervals: [],
circularReferences: []
};
// Simulate memory leaks
// 1. Intentional data retention
for (let i = 0; i < 1000; i++) {
const leakyData = {
id: i,
largeData: new Array(1000).fill(Math.random()),
references: []
};
// Keep reference (simulating leak)
leakTest.intentionalLeaks.push(leakyData);
}
this.takeMemorySnapshot('leak_data_accumulated');
// 2. Event listener leaks
for (let i = 0; i < 100; i++) {
const element = document.createElement('div');
const handler = () => console.log('Leaked handler');
element.addEventListener('click', handler);
// Don't remove listener (simulating leak)
leakTest.eventListeners.push({ element, handler });
}
// 3. Timer leaks
for (let i = 0; i < 10; i++) {
const intervalId = setInterval(() => {
// Do some work that references external data
leakTest.intentionalLeaks.forEach(data => data.accessCount = (data.accessCount || 0) + 1);
}, 100);
leakTest.intervals.push(intervalId);
}
// 4. Circular references
for (let i = 0; i < 500; i++) {
const obj1 = { id: i, type: 'obj1' };
const obj2 = { id: i, type: 'obj2' };
obj1.reference = obj2;
obj2.reference = obj1;
leakTest.circularReferences.push(obj1, obj2);
}
this.takeMemorySnapshot('leak_test_end');
// Cleanup some leaks (but not all)
leakTest.intervals.forEach(id => clearInterval(id));
leakTest.intervals = [];
return leakTest;
}
async testLowMemoryRecovery() {
this.takeMemorySnapshot('recovery_test_start');
const recoveryTest = {
preCleanupMemory: null,
postCleanupMemory: null,
cleanupActions: [],
recoverySuccess: false
};
// Create memory pressure
const pressureData = [];
for (let i = 0; i < 50000; i++) {
pressureData.push({
id: i,
data: new Array(200).fill(Math.random()),
timestamp: Date.now()
});
}
recoveryTest.preCleanupMemory = this.takeMemorySnapshot('pre_cleanup');
// Simulate cleanup actions
const cleanupActions = [
() => {
// Clear large arrays
pressureData.length = Math.floor(pressureData.length * 0.5);
return 'cleared_half_pressure_data';
},
() => {
// Remove unused event listeners
const elements = document.querySelectorAll('.temporary-element');
elements.forEach(el => el.remove());
return `removed_${elements.length}_elements`;
},
() => {
// Clear caches
if (window.testCache) {
window.testCache.clear();
}
return 'cleared_test_cache';
},
() => {
// Force garbage collection hint
if (window.gc) {
window.gc();
return 'forced_gc';
}
return 'gc_not_available';
}
];
for (const action of cleanupActions) {
try {
const result = action();
recoveryTest.cleanupActions.push(result);
await new Promise(resolve => setTimeout(resolve, 50));
} catch (error) {
recoveryTest.cleanupActions.push(`error: ${error.message}`);
}
}
recoveryTest.postCleanupMemory = this.takeMemorySnapshot('post_cleanup');
// Check if recovery was successful
if (recoveryTest.preCleanupMemory.usedJSHeapSize && recoveryTest.postCleanupMemory.usedJSHeapSize) {
const memoryReduced = recoveryTest.preCleanupMemory.usedJSHeapSize > recoveryTest.postCleanupMemory.usedJSHeapSize;
const reductionAmount = recoveryTest.preCleanupMemory.usedJSHeapSize - recoveryTest.postCleanupMemory.usedJSHeapSize;
recoveryTest.recoverySuccess = memoryReduced && reductionAmount > 0;
recoveryTest.memoryReduced = reductionAmount;
recoveryTest.reductionPercentage = (reductionAmount / recoveryTest.preCleanupMemory.usedJSHeapSize) * 100;
}
return recoveryTest;
}
async runAllTests() {
const results = {
startTime: Date.now(),
originalMemory: this.originalMemory,
tests: {}
};
try {
results.tests.gradualIncrease = await this.testGradualMemoryIncrease();
results.tests.fragmentation = await this.testMemoryFragmentation();
results.tests.leakSimulation = await this.testMemoryLeakSimulation();
results.tests.lowMemoryRecovery = await this.testLowMemoryRecovery();
} catch (error) {
results.error = error.message;
}
results.endTime = Date.now();
results.totalDuration = results.endTime - results.startTime;
results.finalMemory = this.getMemoryInfo();
results.memorySnapshots = this.memorySnapshots;
return results;
}
}
const memoryTester = new MemoryPressureTester();
return await memoryTester.runAllTests();
"""
)
assert content.script_result is not None
result = content.script_result
# Verify memory pressure testing
assert 'originalMemory' in result
assert 'tests' in result
assert 'finalMemory' in result
assert 'memorySnapshots' in result
# Check individual tests
tests = result['tests']
# Verify gradual memory increase test
if 'gradualIncrease' in tests:
gradual_test = tests['gradualIncrease']
assert len(gradual_test) >= 4 # Should have tested 4 phases
for phase in gradual_test:
assert 'phase' in phase
assert 'allocations' in phase
assert 'memorySnapshot' in phase
# Verify fragmentation test
if 'fragmentation' in tests:
frag_test = tests['fragmentation']
assert 'allocations' in frag_test
assert 'deallocations' in frag_test
assert 'reallocations' in frag_test
assert frag_test['deallocations'] > 0
assert frag_test['reallocations'] > 0
# Verify leak simulation test
if 'leakSimulation' in tests:
leak_test = tests['leakSimulation']
assert 'intentionalLeaks' in leak_test
assert 'eventListeners' in leak_test
assert 'intervals' in leak_test
assert 'circularReferences' in leak_test
# Verify low memory recovery test
if 'lowMemoryRecovery' in tests:
recovery_test = tests['lowMemoryRecovery']
assert 'preCleanupMemory' in recovery_test
assert 'postCleanupMemory' in recovery_test
assert 'cleanupActions' in recovery_test
assert len(recovery_test['cleanupActions']) > 0
# Check memory snapshots were taken
snapshots = result['memorySnapshots']
assert len(snapshots) > 0
for snapshot in snapshots:
assert 'label' in snapshot
assert 'timestamp' in snapshot
class TestConcurrencyEdgeCases:
"""Test concurrency and threading edge cases."""
@pytest.fixture
def base_url(self):
return "http://localhost:8083"
@pytest.mark.asyncio
async def test_web_worker_edge_cases(self, base_url):
"""Test Web Worker edge cases and limitations."""
content = await get(
f"{base_url}/vue/",
script="""
// Test Web Worker functionality and edge cases
const workerTests = {
support: {
webWorkers: typeof Worker !== 'undefined',
sharedWorkers: typeof SharedWorker !== 'undefined',
serviceWorkers: typeof navigator.serviceWorker !== 'undefined'
},
tests: {},
errors: []
};
if (workerTests.support.webWorkers) {
try {
// Test basic Web Worker creation
const workerCode = `
self.onmessage = function(e) {
const { type, data } = e.data;
switch (type) {
case 'cpu_intensive':
let result = 0;
for (let i = 0; i < data.iterations; i++) {
result += Math.sqrt(i);
}
self.postMessage({ type: 'cpu_result', result, iterations: data.iterations });
break;
case 'memory_test':
const memoryData = new Array(data.size).fill(Math.random());
self.postMessage({ type: 'memory_result', size: memoryData.length });
break;
case 'error_test':
throw new Error('Intentional worker error');
default:
self.postMessage({ type: 'unknown', original: e.data });
}
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);
const testWorker = async (testType, testData) => {
return new Promise((resolve, reject) => {
const worker = new Worker(workerUrl);
const timeout = setTimeout(() => {
worker.terminate();
reject(new Error('Worker timeout'));
}, 5000);
worker.onmessage = (e) => {
clearTimeout(timeout);
worker.terminate();
resolve(e.data);
};
worker.onerror = (error) => {
clearTimeout(timeout);
worker.terminate();
reject(error);
};
worker.postMessage({ type: testType, data: testData });
});
};
// Test CPU-intensive work
const cpuTest = await testWorker('cpu_intensive', { iterations: 100000 });
workerTests.tests.cpuIntensive = {
success: true,
result: cpuTest
};
// Test memory allocation
const memoryTest = await testWorker('memory_test', { size: 10000 });
workerTests.tests.memoryAllocation = {
success: true,
result: memoryTest
};
// Test error handling
try {
await testWorker('error_test', {});
} catch (error) {
workerTests.tests.errorHandling = {
success: true,
errorCaught: true,
errorMessage: error.message || 'Worker error caught'
};
}
// Clean up
URL.revokeObjectURL(workerUrl);
} catch (error) {
workerTests.errors.push(`Web Worker test failed: ${error.message}`);
}
}
// Test SharedWorker if supported
if (workerTests.support.sharedWorkers) {
try {
const sharedWorkerCode = `
const connections = [];
self.onconnect = function(e) {
const port = e.ports[0];
connections.push(port);
port.onmessage = function(event) {
const { type, data } = event.data;
if (type === 'broadcast') {
connections.forEach(p => {
if (p !== port) {
p.postMessage({ type: 'broadcast_received', data });
}
});
}
port.postMessage({ type: 'shared_response', original: event.data });
};
port.start();
};
`;
const sharedBlob = new Blob([sharedWorkerCode], { type: 'application/javascript' });
const sharedWorkerUrl = URL.createObjectURL(sharedBlob);
const sharedWorker = new SharedWorker(sharedWorkerUrl);
const sharedWorkerTest = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('SharedWorker timeout'));
}, 3000);
sharedWorker.port.onmessage = (e) => {
clearTimeout(timeout);
resolve(e.data);
};
sharedWorker.port.onerror = (error) => {
clearTimeout(timeout);
reject(error);
};
sharedWorker.port.start();
sharedWorker.port.postMessage({ type: 'test', data: 'shared worker test' });
});
const sharedResult = await sharedWorkerTest;
workerTests.tests.sharedWorker = {
success: true,
result: sharedResult
};
URL.revokeObjectURL(sharedWorkerUrl);
} catch (error) {
workerTests.errors.push(`SharedWorker test failed: ${error.message}`);
}
}
return workerTests;
"""
)
assert content.script_result is not None
result = content.script_result
# Verify Web Worker support detection
support = result['support']
assert 'webWorkers' in support
assert 'sharedWorkers' in support
assert 'serviceWorkers' in support
# If Web Workers are supported, verify tests
if support['webWorkers']:
tests = result['tests']
if 'cpuIntensive' in tests:
cpu_test = tests['cpuIntensive']
assert cpu_test['success'] is True
assert 'result' in cpu_test
if 'memoryAllocation' in tests:
memory_test = tests['memoryAllocation']
assert memory_test['success'] is True
assert 'result' in memory_test
if 'errorHandling' in tests:
error_test = tests['errorHandling']
assert error_test['success'] is True
assert error_test['errorCaught'] is True
<function_calls>
<invoke name="TodoWrite">
<parameter name="todos">[{"content": "Implement Phase 2: Production Optimization", "status": "in_progress", "activeForm": "Implementing Phase 2: Production Optimization"}, {"content": "Create comprehensive network resilience test suite", "status": "completed", "activeForm": "Creating comprehensive network resilience test suite"}, {"content": "Build platform-specific edge case tests", "status": "completed", "activeForm": "Building platform-specific edge case tests"}, {"content": "Implement performance under pressure test suite", "status": "in_progress", "activeForm": "Implementing performance under pressure test suite"}, {"content": "Create browser engine compatibility tests", "status": "pending", "activeForm": "Creating browser engine compatibility tests"}, {"content": "Build memory management and leak detection tests", "status": "pending", "activeForm": "Building memory management and leak detection tests"}]