@memberjunction/react-test-harness 2.95.0 → 2.97.0
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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/component-linter.d.ts +14 -7
- package/dist/lib/component-linter.d.ts.map +1 -1
- package/dist/lib/component-linter.js +1225 -311
- package/dist/lib/component-linter.js.map +1 -1
- package/dist/lib/component-runner.d.ts +1 -3
- package/dist/lib/component-runner.d.ts.map +1 -1
- package/dist/lib/component-runner.js +190 -64
- package/dist/lib/component-runner.js.map +1 -1
- package/dist/lib/styles-type-analyzer.d.ts +64 -0
- package/dist/lib/styles-type-analyzer.d.ts.map +1 -0
- package/dist/lib/styles-type-analyzer.js +265 -0
- package/dist/lib/styles-type-analyzer.js.map +1 -0
- package/dist/lib/test-broken-7.d.ts +2 -0
- package/dist/lib/test-broken-7.d.ts.map +1 -0
- package/dist/lib/test-broken-7.js +73 -0
- package/dist/lib/test-broken-7.js.map +1 -0
- package/dist/lib/test-harness.d.ts.map +1 -1
- package/dist/lib/test-harness.js +1 -2
- package/dist/lib/test-harness.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { BrowserManager } from './browser-context';
|
|
3
3
|
import type { UserInfo } from '@memberjunction/core';
|
|
4
|
-
import {
|
|
4
|
+
import { Violation } from './component-linter';
|
|
5
5
|
import { ComponentSpec, ComponentUtilities, SimpleAITools } from '@memberjunction/interactive-component-types';
|
|
6
6
|
export interface ComponentExecutionOptions {
|
|
7
7
|
componentSpec: ComponentSpec;
|
|
@@ -31,7 +31,6 @@ export interface ComponentExecutionResult {
|
|
|
31
31
|
executionTime: number;
|
|
32
32
|
renderCount?: number;
|
|
33
33
|
lintViolations?: Violation[];
|
|
34
|
-
fixSuggestions?: FixSuggestion[];
|
|
35
34
|
}
|
|
36
35
|
/**
|
|
37
36
|
* ComponentRunner that uses the actual React runtime via Playwright UMD bundle
|
|
@@ -46,7 +45,6 @@ export declare class ComponentRunner {
|
|
|
46
45
|
*/
|
|
47
46
|
lintComponent(componentCode: string, componentName: string, componentSpec?: any, isRootComponent?: boolean, contextUser?: UserInfo, options?: any): Promise<{
|
|
48
47
|
violations: Violation[];
|
|
49
|
-
suggestions: FixSuggestion[];
|
|
50
48
|
hasErrors: boolean;
|
|
51
49
|
}>;
|
|
52
50
|
executeComponent(options: ComponentExecutionOptions): Promise<ComponentExecutionResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAiC,QAAQ,EAAyD,MAAM,sBAAsB,CAAC;AAC3I,OAAO,EAAmB,
|
|
1
|
+
{"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAiC,QAAQ,EAAyD,MAAM,sBAAsB,CAAC;AAC3I,OAAO,EAAmB,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,aAAa,EAMd,MAAM,6CAA6C,CAAC;AAMrD,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,aAAa,CAAC;IAC/D,WAAW,EAAE,QAAQ,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,kBAAkB,CAAC;CAChC;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,SAAS,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,qBAAa,eAAe;IAkBd,OAAO,CAAC,cAAc;IAhBlC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAS/C;IAKF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAE7B,cAAc,EAAE,cAAc;IAElD;;OAEG;IACG,aAAa,CACjB,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,EACrB,aAAa,CAAC,EAAE,GAAG,EACnB,eAAe,CAAC,EAAE,OAAO,EACzB,WAAW,CAAC,EAAE,QAAQ,EACtB,OAAO,CAAC,EAAE,GAAG,GACZ,OAAO,CAAC;QAAE,UAAU,EAAE,SAAS,EAAE,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAmBrD,gBAAgB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IA6sB7F;;;OAGG;YACW,oBAAoB;IA0ElC;;OAEG;YACW,sBAAsB;IAqHpC;;OAEG;YACW,kBAAkB;IAiFhC;;OAEG;YACW,oBAAoB;IA8ClC;;OAEG;YACW,eAAe;IAkB7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;YA+Bb,qBAAqB;cAqBnB,uBAAuB,CAAC,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IA2JtF;;OAEG;YACW,iBAAiB;IAsT/B;;OAEG;IACH,OAAO,CAAC,aAAa;CA6BtB"}
|
|
@@ -45,7 +45,6 @@ class ComponentRunner {
|
|
|
45
45
|
const hasErrors = lintResult.violations.some(v => v.severity === 'critical' || v.severity === 'high');
|
|
46
46
|
return {
|
|
47
47
|
violations: lintResult.violations,
|
|
48
|
-
suggestions: lintResult.suggestions,
|
|
49
48
|
hasErrors
|
|
50
49
|
};
|
|
51
50
|
}
|
|
@@ -131,22 +130,25 @@ class ComponentRunner {
|
|
|
131
130
|
console.log(' - spec.name:', options.componentSpec.name);
|
|
132
131
|
console.log(' - spec.code length:', options.componentSpec.code?.length || 0);
|
|
133
132
|
console.log(' - props:', JSON.stringify(options.props || {}, null, 2));
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
console.log(' -
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
// Show spec-specific libraries, not all available libraries
|
|
134
|
+
if (options.componentSpec.libraries && options.componentSpec.libraries.length > 0) {
|
|
135
|
+
console.log(' - spec requires libraries:', options.componentSpec.libraries.map(lib => ({
|
|
136
|
+
name: lib.name,
|
|
137
|
+
globalVariable: lib.globalVariable,
|
|
138
|
+
version: lib.version
|
|
139
139
|
})));
|
|
140
140
|
}
|
|
141
|
+
else {
|
|
142
|
+
console.log(' - spec requires libraries: none');
|
|
143
|
+
}
|
|
144
|
+
// Total available libraries in metadata (for context only)
|
|
145
|
+
console.log(' - total available libraries in metadata:', allLibraries?.length || 0);
|
|
141
146
|
}
|
|
142
147
|
// Execute the component using the real React runtime with timeout (Recommendation #1)
|
|
143
148
|
const executionPromise = page.evaluate(async ({ spec, props, debug, componentLibraries }) => {
|
|
144
149
|
if (debug) {
|
|
145
150
|
console.log('🎯 Starting component execution');
|
|
146
|
-
console.log('📚 BROWSER:
|
|
147
|
-
if (componentLibraries?.length > 0) {
|
|
148
|
-
console.log(' First library:', componentLibraries[0]);
|
|
149
|
-
}
|
|
151
|
+
console.log('📚 BROWSER: Component libraries available for loading:', componentLibraries?.length || 0);
|
|
150
152
|
}
|
|
151
153
|
// Declare renderCheckInterval at the top scope for cleanup
|
|
152
154
|
let renderCheckInterval;
|
|
@@ -215,8 +217,15 @@ class ComponentRunner {
|
|
|
215
217
|
// The spec might not have globalVariable, but we need it for the runtime to work
|
|
216
218
|
if (spec.libraries && componentLibraries) {
|
|
217
219
|
for (const specLib of spec.libraries) {
|
|
220
|
+
// Skip if library entry is invalid
|
|
221
|
+
if (!specLib || !specLib.name) {
|
|
222
|
+
if (debug) {
|
|
223
|
+
console.warn(' ⚠️ Skipping invalid library entry (missing name):', specLib);
|
|
224
|
+
}
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
218
227
|
if (!specLib.globalVariable) {
|
|
219
|
-
const libDef = componentLibraries.find(l => l.Name.toLowerCase() === specLib.name.toLowerCase());
|
|
228
|
+
const libDef = componentLibraries.find(l => l && l.Name && l.Name.toLowerCase() === specLib.name.toLowerCase());
|
|
220
229
|
if (libDef && libDef.GlobalVariable) {
|
|
221
230
|
specLib.globalVariable = libDef.GlobalVariable;
|
|
222
231
|
if (debug) {
|
|
@@ -257,10 +266,17 @@ class ComponentRunner {
|
|
|
257
266
|
message: `Component registration failed: ${registrationError.message || registrationError}`,
|
|
258
267
|
stack: registrationError.stack,
|
|
259
268
|
type: 'registration-error',
|
|
260
|
-
phase: 'component-compilation'
|
|
269
|
+
phase: 'component-compilation',
|
|
270
|
+
source: 'runtime-wrapper'
|
|
261
271
|
});
|
|
262
272
|
window.__testHarnessTestFailed = true;
|
|
263
|
-
|
|
273
|
+
// Don't re-throw - let execution continue to collect this error properly
|
|
274
|
+
// Return a failure result so the Promise resolves properly
|
|
275
|
+
return {
|
|
276
|
+
success: false,
|
|
277
|
+
error: `Component registration failed: ${registrationError.message || registrationError}`,
|
|
278
|
+
componentCount: 0
|
|
279
|
+
};
|
|
264
280
|
}
|
|
265
281
|
if (debug && !registrationResult.success) {
|
|
266
282
|
console.log('❌ Registration failed:', registrationResult.errors);
|
|
@@ -319,7 +335,8 @@ class ComponentRunner {
|
|
|
319
335
|
window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
|
|
320
336
|
window.__testHarnessRuntimeErrors.push({
|
|
321
337
|
message: `Likely infinite render loop: ${currentRenderCount} createElement calls (max: ${MAX_RENDERS_ALLOWED})`,
|
|
322
|
-
type: 'render-loop'
|
|
338
|
+
type: 'render-loop',
|
|
339
|
+
source: 'test-harness'
|
|
323
340
|
});
|
|
324
341
|
// Try to unmount to stop the madness
|
|
325
342
|
try {
|
|
@@ -355,18 +372,29 @@ class ComponentRunner {
|
|
|
355
372
|
static getDerivedStateFromError(error) {
|
|
356
373
|
// Capture the actual error message IMMEDIATELY
|
|
357
374
|
window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
|
|
375
|
+
// Check if this is a minified React error
|
|
376
|
+
const errorMessage = error.message || error.toString();
|
|
377
|
+
let enhancedMessage = errorMessage;
|
|
378
|
+
if (errorMessage.includes('Minified React error')) {
|
|
379
|
+
// Extract error number if present
|
|
380
|
+
const match = errorMessage.match(/#(\d+)/);
|
|
381
|
+
if (match) {
|
|
382
|
+
enhancedMessage = `React Error #${match[1]} - Visit https://react.dev/errors/${match[1]} for details. ${errorMessage}`;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
358
385
|
window.__testHarnessRuntimeErrors.push({
|
|
359
|
-
message:
|
|
386
|
+
message: enhancedMessage,
|
|
360
387
|
stack: error.stack,
|
|
361
388
|
type: 'react-render-error',
|
|
362
|
-
phase: 'component-render'
|
|
389
|
+
phase: 'component-render',
|
|
390
|
+
source: 'user-component' // This is the actual error from user's component
|
|
363
391
|
});
|
|
364
392
|
window.__testHarnessTestFailed = true;
|
|
365
393
|
return { hasError: true, error };
|
|
366
394
|
}
|
|
367
395
|
componentDidCatch(error, errorInfo) {
|
|
368
|
-
|
|
369
|
-
//
|
|
396
|
+
// Don't log here - it creates duplicate messages
|
|
397
|
+
// Just update the last error with component stack info
|
|
370
398
|
const errors = window.__testHarnessRuntimeErrors || [];
|
|
371
399
|
if (errors.length > 0) {
|
|
372
400
|
const lastError = errors[errors.length - 1];
|
|
@@ -378,8 +406,8 @@ class ComponentRunner {
|
|
|
378
406
|
render() {
|
|
379
407
|
if (this.state.hasError) {
|
|
380
408
|
// DON'T re-throw - this causes "Script error"
|
|
381
|
-
// Instead, render
|
|
382
|
-
return
|
|
409
|
+
// Instead, render a simple error indicator
|
|
410
|
+
return null; // Don't render anything - the error is already captured
|
|
383
411
|
}
|
|
384
412
|
return this.props.children;
|
|
385
413
|
}
|
|
@@ -411,7 +439,8 @@ class ComponentRunner {
|
|
|
411
439
|
window.__testHarnessRuntimeErrors.push({
|
|
412
440
|
message: error.message || String(error),
|
|
413
441
|
stack: error.stack,
|
|
414
|
-
type: 'execution-error'
|
|
442
|
+
type: 'execution-error',
|
|
443
|
+
source: 'runtime-wrapper'
|
|
415
444
|
});
|
|
416
445
|
window.__testHarnessTestFailed = true;
|
|
417
446
|
return {
|
|
@@ -429,17 +458,34 @@ class ComponentRunner {
|
|
|
429
458
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Component execution timeout after ${globalTimeout}ms`)), globalTimeout));
|
|
430
459
|
// Race between execution and timeout
|
|
431
460
|
let executionResult;
|
|
461
|
+
let hasTimeout = false;
|
|
432
462
|
try {
|
|
433
463
|
executionResult = await Promise.race([executionPromise, timeoutPromise]);
|
|
434
464
|
}
|
|
435
465
|
catch (timeoutError) {
|
|
436
466
|
// Handle timeout gracefully
|
|
467
|
+
hasTimeout = true;
|
|
437
468
|
errors.push(`Component execution timed out after ${globalTimeout}ms`);
|
|
438
469
|
executionResult = {
|
|
439
470
|
success: false,
|
|
440
471
|
error: timeoutError instanceof Error ? timeoutError.message : 'Execution timeout'
|
|
441
472
|
};
|
|
442
473
|
}
|
|
474
|
+
// Ensure executionResult has proper shape
|
|
475
|
+
if (!executionResult) {
|
|
476
|
+
executionResult = {
|
|
477
|
+
success: false,
|
|
478
|
+
error: 'Component execution returned no result'
|
|
479
|
+
};
|
|
480
|
+
errors.push('Component execution failed to return a result');
|
|
481
|
+
}
|
|
482
|
+
else if (!executionResult.success && executionResult.error) {
|
|
483
|
+
// Add the execution error if it hasn't been captured elsewhere
|
|
484
|
+
const errorMsg = `Component execution failed: ${executionResult.error}`;
|
|
485
|
+
if (!errors.some(e => e.includes(executionResult.error))) {
|
|
486
|
+
errors.push(errorMsg);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
443
489
|
if (debug) {
|
|
444
490
|
console.log('Execution result:', executionResult);
|
|
445
491
|
}
|
|
@@ -448,9 +494,9 @@ class ComponentRunner {
|
|
|
448
494
|
await page.waitForTimeout(renderWaitTime);
|
|
449
495
|
// Get render count
|
|
450
496
|
renderCount = await page.evaluate(() => window.__testHarnessRenderCount || 0);
|
|
451
|
-
// Collect all errors
|
|
452
|
-
const
|
|
453
|
-
errors.push(...
|
|
497
|
+
// Collect all errors with source information
|
|
498
|
+
const runtimeErrorsWithSource = await this.collectRuntimeErrors(page);
|
|
499
|
+
errors.push(...runtimeErrorsWithSource.map(e => e.message)); // Extract messages for backward compat
|
|
454
500
|
// Collect warnings (separate from errors)
|
|
455
501
|
const collectedWarnings = await this.collectWarnings(page);
|
|
456
502
|
warnings.push(...collectedWarnings);
|
|
@@ -460,8 +506,9 @@ class ComponentRunner {
|
|
|
460
506
|
const asyncErrors = await this.collectRuntimeErrors(page);
|
|
461
507
|
// Only add new errors
|
|
462
508
|
asyncErrors.forEach(err => {
|
|
463
|
-
if (!errors.includes(err)) {
|
|
464
|
-
errors.push(err);
|
|
509
|
+
if (!errors.includes(err.message)) {
|
|
510
|
+
errors.push(err.message);
|
|
511
|
+
runtimeErrorsWithSource.push(err); // Keep the structured version too
|
|
465
512
|
}
|
|
466
513
|
});
|
|
467
514
|
// Also check for new warnings
|
|
@@ -475,26 +522,65 @@ class ComponentRunner {
|
|
|
475
522
|
const html = await page.content();
|
|
476
523
|
// Take screenshot
|
|
477
524
|
const screenshot = await page.screenshot();
|
|
525
|
+
// Check for excessive render count first
|
|
526
|
+
const hasRenderLoop = renderCount > ComponentRunner.MAX_RENDER_COUNT;
|
|
527
|
+
if (hasRenderLoop) {
|
|
528
|
+
errors.push(`Possible render loop: ${renderCount} createElement calls detected (likely infinite loop)`);
|
|
529
|
+
}
|
|
478
530
|
// Determine success
|
|
479
531
|
const success = errors.length === 0 &&
|
|
480
532
|
criticalWarnings.length === 0 &&
|
|
481
|
-
|
|
533
|
+
!hasRenderLoop &&
|
|
534
|
+
!hasTimeout &&
|
|
482
535
|
executionResult.success;
|
|
483
|
-
if (renderCount > ComponentRunner.MAX_RENDER_COUNT) {
|
|
484
|
-
errors.push(`Possible render loop: ${renderCount} createElement calls detected (likely infinite loop)`);
|
|
485
|
-
}
|
|
486
536
|
// Combine runtime errors with data errors
|
|
487
537
|
const allErrors = [...errors, ...dataErrors];
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
538
|
+
// Map runtime errors with source info, data errors don't have source
|
|
539
|
+
const errorViolations = runtimeErrorsWithSource.map(e => ({
|
|
540
|
+
message: e.message,
|
|
541
|
+
severity: 'critical',
|
|
542
|
+
rule: 'runtime-error',
|
|
543
|
+
line: 0,
|
|
544
|
+
column: 0,
|
|
545
|
+
source: e.source
|
|
546
|
+
}));
|
|
547
|
+
// Add timeout error if detected
|
|
548
|
+
if (hasTimeout) {
|
|
549
|
+
errorViolations.push({
|
|
550
|
+
message: `Component execution timed out after ${globalTimeout}ms`,
|
|
551
|
+
severity: 'critical',
|
|
552
|
+
rule: 'timeout',
|
|
553
|
+
line: 0,
|
|
554
|
+
column: 0,
|
|
555
|
+
source: 'test-harness' // This is a test harness timeout
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
// Add render loop error if detected
|
|
559
|
+
if (hasRenderLoop) {
|
|
560
|
+
errorViolations.push({
|
|
561
|
+
message: `Possible render loop: ${renderCount} createElement calls detected (likely infinite loop)`,
|
|
562
|
+
severity: 'critical',
|
|
563
|
+
rule: 'render-loop',
|
|
564
|
+
line: 0,
|
|
565
|
+
column: 0,
|
|
566
|
+
source: 'test-harness' // This is a test harness detection
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
// Add data errors without source
|
|
570
|
+
dataErrors.forEach(e => {
|
|
571
|
+
errorViolations.push({
|
|
492
572
|
message: e,
|
|
493
573
|
severity: 'critical',
|
|
494
574
|
rule: 'runtime-error',
|
|
495
575
|
line: 0,
|
|
496
|
-
column: 0
|
|
497
|
-
|
|
576
|
+
column: 0,
|
|
577
|
+
source: 'user-component' // Data errors are from user's data access code
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
const result = {
|
|
581
|
+
success: success && dataErrors.length === 0, // Fail if we have data errors
|
|
582
|
+
html,
|
|
583
|
+
errors: errorViolations,
|
|
498
584
|
warnings: warnings.map(w => ({
|
|
499
585
|
message: w,
|
|
500
586
|
severity: 'low',
|
|
@@ -514,19 +600,35 @@ class ComponentRunner {
|
|
|
514
600
|
return result;
|
|
515
601
|
}
|
|
516
602
|
catch (error) {
|
|
517
|
-
errors
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
603
|
+
// For catch block errors, we need to handle them specially
|
|
604
|
+
const catchError = {
|
|
605
|
+
message: error instanceof Error ? error.message : String(error),
|
|
606
|
+
source: 'test-harness' // Errors caught here are usually test harness issues
|
|
607
|
+
};
|
|
608
|
+
// Create error violations including the catch error
|
|
609
|
+
const errorViolations = [{
|
|
610
|
+
message: catchError.message,
|
|
611
|
+
severity: 'critical',
|
|
612
|
+
rule: 'runtime-error',
|
|
613
|
+
line: 0,
|
|
614
|
+
column: 0,
|
|
615
|
+
source: catchError.source
|
|
616
|
+
}];
|
|
617
|
+
// Add any data errors
|
|
618
|
+
dataErrors.forEach(e => {
|
|
619
|
+
errorViolations.push({
|
|
524
620
|
message: e,
|
|
525
621
|
severity: 'critical',
|
|
526
622
|
rule: 'runtime-error',
|
|
527
623
|
line: 0,
|
|
528
|
-
column: 0
|
|
529
|
-
|
|
624
|
+
column: 0,
|
|
625
|
+
source: 'user-component'
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
const result = {
|
|
629
|
+
success: false,
|
|
630
|
+
html: '',
|
|
631
|
+
errors: errorViolations,
|
|
530
632
|
warnings: warnings.map(w => ({
|
|
531
633
|
message: w,
|
|
532
634
|
severity: 'low',
|
|
@@ -565,7 +667,12 @@ class ComponentRunner {
|
|
|
565
667
|
* Component-specific libraries are loaded by the runtime's ComponentCompiler
|
|
566
668
|
*/
|
|
567
669
|
async loadRuntimeLibraries(page, debug = false) {
|
|
568
|
-
//
|
|
670
|
+
// Import getCoreRuntimeLibraries to get the correct URLs for React libraries
|
|
671
|
+
// We can't use LibraryLoader.loadAllLibraries() here because it expects to run in a browser
|
|
672
|
+
// environment with window/document, but we're in Node.js with Playwright
|
|
673
|
+
const { getCoreRuntimeLibraries } = await Promise.resolve().then(() => __importStar(require('@memberjunction/react-runtime')));
|
|
674
|
+
const coreLibraries = getCoreRuntimeLibraries(debug);
|
|
675
|
+
// Helper function to load scripts with timeout
|
|
569
676
|
const loadScriptWithTimeout = async (url, timeout = 10000) => {
|
|
570
677
|
try {
|
|
571
678
|
await Promise.race([
|
|
@@ -577,11 +684,15 @@ class ComponentRunner {
|
|
|
577
684
|
throw new Error(`Failed to load script ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
578
685
|
}
|
|
579
686
|
};
|
|
580
|
-
// Load React
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
687
|
+
// Load the core libraries (React, ReactDOM, Babel) into the Playwright page context
|
|
688
|
+
for (const lib of coreLibraries) {
|
|
689
|
+
if (lib.cdnUrl) {
|
|
690
|
+
await loadScriptWithTimeout(lib.cdnUrl);
|
|
691
|
+
if (debug) {
|
|
692
|
+
console.log(` ✓ Loaded ${lib.displayName} (${lib.globalVariable})`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
585
696
|
// Load the real MemberJunction React Runtime UMD bundle
|
|
586
697
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
587
698
|
// Resolve the path to the UMD bundle
|
|
@@ -768,10 +879,16 @@ class ComponentRunner {
|
|
|
768
879
|
};
|
|
769
880
|
// Global error handler
|
|
770
881
|
window.addEventListener('error', (event) => {
|
|
882
|
+
// Determine source based on error content
|
|
883
|
+
let source = 'user-component'; // Default to user component
|
|
884
|
+
if (event.message && event.message.includes('Script error')) {
|
|
885
|
+
source = 'runtime-wrapper';
|
|
886
|
+
}
|
|
771
887
|
window.__testHarnessRuntimeErrors.push({
|
|
772
888
|
message: event.error?.message || event.message,
|
|
773
889
|
stack: event.error?.stack,
|
|
774
|
-
type: 'runtime'
|
|
890
|
+
type: 'runtime',
|
|
891
|
+
source: source
|
|
775
892
|
});
|
|
776
893
|
window.__testHarnessTestFailed = true;
|
|
777
894
|
});
|
|
@@ -780,7 +897,8 @@ class ComponentRunner {
|
|
|
780
897
|
window.__testHarnessRuntimeErrors.push({
|
|
781
898
|
message: 'Unhandled Promise Rejection: ' + (event.reason?.message || event.reason),
|
|
782
899
|
stack: event.reason?.stack,
|
|
783
|
-
type: 'promise-rejection'
|
|
900
|
+
type: 'promise-rejection',
|
|
901
|
+
source: 'user-component' // Async errors are likely from user code
|
|
784
902
|
});
|
|
785
903
|
window.__testHarnessTestFailed = true;
|
|
786
904
|
event.preventDefault();
|
|
@@ -799,23 +917,31 @@ class ComponentRunner {
|
|
|
799
917
|
};
|
|
800
918
|
});
|
|
801
919
|
const errors = [];
|
|
802
|
-
//
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
920
|
+
// Check if we have any specific React render errors
|
|
921
|
+
const hasSpecificReactError = errorData.runtimeErrors.some((error) => error.type === 'react-render-error' &&
|
|
922
|
+
!error.message.includes('Script error'));
|
|
923
|
+
// Process runtime errors with their source information
|
|
806
924
|
errorData.runtimeErrors.forEach((error) => {
|
|
807
|
-
//
|
|
925
|
+
// Skip generic "Script error" messages if we have more specific React errors
|
|
926
|
+
if (hasSpecificReactError &&
|
|
927
|
+
error.type === 'runtime' &&
|
|
928
|
+
error.message === 'Script error.') {
|
|
929
|
+
return; // Skip this generic error
|
|
930
|
+
}
|
|
808
931
|
const phase = error.phase ? ` (during ${error.phase})` : '';
|
|
809
932
|
const errorMsg = `${error.type} error: ${error.message}${phase}`;
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
933
|
+
errors.push({
|
|
934
|
+
message: errorMsg,
|
|
935
|
+
source: error.source
|
|
936
|
+
});
|
|
813
937
|
});
|
|
938
|
+
// Console errors don't have source info
|
|
814
939
|
errorData.consoleErrors.forEach((error) => {
|
|
815
940
|
const errorMsg = `Console error: ${error}`;
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
941
|
+
errors.push({
|
|
942
|
+
message: errorMsg,
|
|
943
|
+
source: 'react-framework' // Console errors from React are framework level
|
|
944
|
+
});
|
|
819
945
|
});
|
|
820
946
|
return errors;
|
|
821
947
|
}
|