Skip to content

Commit cece758

Browse files
committed
show rendering errors on terminal
1 parent 9ea1aee commit cece758

File tree

5 files changed

+184
-6
lines changed

5 files changed

+184
-6
lines changed

src/lib/ctx.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ export default (options: Record<string, string>): Context => {
120120
}
121121

122122
//if config.waitForPageRender has value and if its less than 30000 then make it to 30000 default
123-
if (config.waitForPageRender && config.waitForPageRender < 30000) {
124-
config.waitForPageRender = 30000;
125-
}
123+
// if (config.waitForPageRender && config.waitForPageRender < 30000) {
124+
// config.waitForPageRender = 30000;
125+
// }
126126

127127
return {
128128
env: env,

src/lib/env.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ export default (): Env => {
2222
SMARTUI_API_PROXY,
2323
SMARTUI_API_SKIP_CERTIFICATES,
2424
USE_REMOTE_DISCOVERY,
25-
SMART_GIT
25+
SMART_GIT,
26+
SHOW_RENDER_ERRORS,
27+
SMARTUI_SSE_URL='https://server-events.lambdatest.com'
2628
} = process.env
2729

2830
return {
@@ -46,6 +48,8 @@ export default (): Env => {
4648
SMARTUI_API_PROXY,
4749
SMARTUI_API_SKIP_CERTIFICATES: SMARTUI_API_SKIP_CERTIFICATES === 'true',
4850
USE_REMOTE_DISCOVERY: USE_REMOTE_DISCOVERY === 'true',
49-
SMART_GIT: SMART_GIT === 'true'
51+
SMART_GIT: SMART_GIT === 'true',
52+
SHOW_RENDER_ERRORS: SHOW_RENDER_ERRORS === 'true',
53+
SMARTUI_SSE_URL
5054
}
5155
}

src/lib/utils.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,4 +740,168 @@ export function validateCoordinates(
740740
valid: true,
741741
coords: { top, bottom, left, right }
742742
};
743+
}
744+
745+
export function createBasicAuthToken(username: string, accessKey: string): string {
746+
const credentials = `${username}:${accessKey}`;
747+
return Buffer.from(credentials).toString('base64');
748+
}
749+
750+
export async function listenToSmartUISSE(
751+
baseURL: string,
752+
accessToken: string,
753+
onEvent?: (eventType: string, data: any) => void
754+
): Promise<{ abort: () => void }> {
755+
const url = `${baseURL}/api/v1/sse/smartui`;
756+
757+
const abortController = new AbortController();
758+
759+
try {
760+
const response = await fetch(url, {
761+
method: 'GET',
762+
headers: {
763+
'Accept': 'text/event-stream',
764+
'Cache-Control': 'no-cache',
765+
'Cookie': `stageAccessToken=Basic ${accessToken}`
766+
},
767+
signal: abortController.signal
768+
});
769+
770+
if (!response.ok) {
771+
throw new Error(`HTTP error! status: ${response.status}`);
772+
}
773+
774+
onEvent?.('open', { status: 'connected' });
775+
776+
const reader = response.body?.getReader();
777+
if (!reader) {
778+
throw new Error('No response body reader available');
779+
}
780+
781+
const decoder = new TextDecoder();
782+
let buffer = '';
783+
let currentEvent = '';
784+
785+
try {
786+
while (true) {
787+
const { done, value } = await reader.read();
788+
if (done) break;
789+
790+
const chunk = decoder.decode(value, { stream: true });
791+
792+
buffer += chunk;
793+
const lines = buffer.split('\n');
794+
795+
buffer = lines.pop() || '';
796+
797+
for (const line of lines) {
798+
if (line.startsWith('event:')) {
799+
currentEvent = line.substring(6).trim();
800+
}
801+
else if (line.startsWith('data:')) {
802+
const data = line.substring(5).trim();
803+
804+
if (data) {
805+
try {
806+
const parsedData = JSON.parse(data);
807+
onEvent?.(currentEvent, parsedData);
808+
} catch (parseError) {
809+
if (currentEvent === 'connection' && data === 'connected') {
810+
onEvent?.(currentEvent, { status: 'connected', message: data });
811+
} else {
812+
onEvent?.(currentEvent, data);
813+
}
814+
}
815+
}
816+
}
817+
else if (line.trim() === '') {
818+
currentEvent = '';
819+
}
820+
}
821+
}
822+
} catch (streamError: any) {
823+
console.error('SSE Streaming error:', streamError);
824+
onEvent?.('error', streamError);
825+
} finally {
826+
reader.releaseLock();
827+
}
828+
829+
} catch (error) {
830+
console.error('SSE Connection error:', error);
831+
onEvent?.('error', error);
832+
}
833+
834+
return {
835+
abort: () => abortController.abort()
836+
};
837+
}
838+
839+
export async function startSSEListener(ctx: Context) {
840+
let retryCount = 0;
841+
const maxRetries = 3;
842+
let currentConnection: { abort: () => void } | null = null;
843+
let errorCount = 0;
844+
845+
const connectWithRetry = async () => {
846+
try {
847+
ctx.log.debug(`Attempting SSE connection (attempt ${retryCount + 1}/${maxRetries})`);
848+
const accessKey = ctx.env.LT_ACCESS_KEY;
849+
const username = ctx.env.LT_USERNAME;
850+
851+
const basicAuthToken = createBasicAuthToken(username, accessKey);
852+
ctx.log.debug(`Basic auth token: ${basicAuthToken}`);
853+
currentConnection = await listenToSmartUISSE(
854+
ctx.env.SMARTUI_SSE_URL,
855+
basicAuthToken,
856+
(eventType, data) => {
857+
switch (eventType) {
858+
case 'open':
859+
ctx.log.debug('Connected to SSE server');
860+
retryCount = 0;
861+
break;
862+
863+
case 'connection':
864+
ctx.log.debug('Connection confirmed:', data);
865+
retryCount = 0;
866+
break;
867+
868+
case 'Dot_buildCompleted':
869+
ctx.log.debug('Build completed');
870+
console.log('Build completed');
871+
currentConnection?.abort();
872+
if(errorCount > 0) {
873+
process.exit(1);
874+
}
875+
process.exit(0);
876+
877+
case 'DOTUIError':
878+
if (data.buildId== ctx.build.id) {
879+
errorCount++;
880+
console.error('Error in build:', data.message);
881+
}
882+
break;
883+
884+
case 'error':
885+
ctx.log.debug('SSE Error occurred:', data);
886+
currentConnection?.abort();
887+
process.exit(0);
888+
889+
}
890+
}
891+
);
892+
893+
} catch (error) {
894+
ctx.log.debug(`Failed to start SSE listener (attempt ${retryCount + 1}):`, error);
895+
retryCount++;
896+
897+
if (retryCount < maxRetries) {
898+
ctx.log.debug(`Retrying in 2 seconds...`);
899+
setTimeout(connectWithRetry, 2000);
900+
} else {
901+
ctx.log.debug('Max retries reached. SSE listener failed.');
902+
}
903+
}
904+
};
905+
906+
connectWithRetry();
743907
}

src/tasks/exec.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Context } from '../types.js'
33
import chalk from 'chalk'
44
import spawn from 'cross-spawn'
55
import { updateLogContext } from '../lib/logger.js'
6-
import { startPolling } from '../lib/utils.js'
6+
import { startPolling, startSSEListener } from '../lib/utils.js'
77

88
export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRendererFactory> => {
99
return {
@@ -16,6 +16,14 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
1616
}
1717
}
1818

19+
if(ctx.env.SHOW_RENDER_ERRORS && ctx.build && ctx.build.id) {
20+
if(ctx.env.LT_USERNAME&&ctx.env.LT_ACCESS_KEY) {
21+
startSSEListener(ctx);
22+
} else {
23+
console.log('LT_USERNAME and LT_ACCESS_KEY are not set, set them to display render errors');
24+
}
25+
}
26+
1927
updateLogContext({task: 'exec'});
2028

2129
return new Promise((resolve, reject) => {

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ export interface Env {
120120
SMARTUI_API_SKIP_CERTIFICATES: boolean;
121121
USE_REMOTE_DISCOVERY: boolean;
122122
SMART_GIT: boolean;
123+
SHOW_RENDER_ERRORS: boolean;
124+
SMARTUI_SSE_URL: string;
123125
}
124126

125127
export interface Snapshot {

0 commit comments

Comments
 (0)