Skip to content

Commit 7e52cea

Browse files
authored
Merge pull request #395 from Nick-1234531/revert-384-revert-379-DOT-6238
Revert "Revert "show rendering errors on terminal""
2 parents 5332c58 + f9d894d commit 7e52cea

File tree

8 files changed

+178
-6
lines changed

8 files changed

+178
-6
lines changed

src/commander/exec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ command
2525
.option('--scheduled <string>', 'Specify the schedule ID')
2626
.option('--userName <string>', 'Specify the LT username')
2727
.option('--accessKey <string>', 'Specify the LT accesskey')
28+
.option('--show-render-errors', 'Show render errors from SmartUI build')
2829
.action(async function(execCommand, _, command) {
2930
const options = command.optsWithGlobals();
3031
if (options.buildName === '') {

src/lib/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export default {
2525
waitForTimeout: 1000,
2626
enableJavaScript: false,
2727
allowedHostnames: [],
28-
smartIgnore: false
28+
smartIgnore: false,
29+
showRenderErrors: false
2930
},
3031
DEFAULT_WEB_STATIC_CONFIG: [
3132
{

src/lib/ctx.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export default (options: Record<string, string>): Context => {
155155
loadDomContent: loadDomContent,
156156
approvalThreshold: config.approvalThreshold,
157157
rejectionThreshold: config.rejectionThreshold,
158+
showRenderErrors: config.showRenderErrors ?? false
158159
},
159160
uploadFilePath: '',
160161
webStaticConfig: [],
@@ -192,7 +193,8 @@ export default (options: Record<string, string>): Context => {
192193
fetchResultsFileName: fetchResultsFileObj,
193194
baselineBranch: options.baselineBranch || '',
194195
baselineBuild: options.baselineBuild || '',
195-
githubURL : options.githubURL || ''
196+
githubURL : options.githubURL || '',
197+
showRenderErrors: options.showRenderErrors ? true : false
196198
},
197199
cliVersion: version,
198200
totalSnapshots: -1,

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/schemaValidation.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ const ConfigSchema = {
295295
minimum: 0,
296296
maximum: 100,
297297
errorMessage: "Invalid config; rejectionThreshold must be a number"
298+
},
299+
showRenderErrors: {
300+
type: "boolean",
301+
errorMessage: "Invalid config; showRenderErrors must be true/false"
298302
}
299303
},
300304
anyOf: [

src/lib/utils.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,4 +740,152 @@ 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+
ctx: Context,
754+
onEvent?: (eventType: string, data: any) => void
755+
): Promise<{ abort: () => void }> {
756+
const url = `${baseURL}/api/v1/sse/smartui`;
757+
758+
const abortController = new AbortController();
759+
760+
try {
761+
const response = await fetch(url, {
762+
method: 'GET',
763+
headers: {
764+
'Accept': 'text/event-stream',
765+
'Cache-Control': 'no-cache',
766+
'Cookie': `stageAccessToken=Basic ${accessToken}`
767+
},
768+
signal: abortController.signal
769+
});
770+
771+
if (!response.ok) {
772+
throw new Error(`HTTP error! status: ${response.status}`);
773+
}
774+
775+
onEvent?.('open', { status: 'connected' });
776+
777+
const reader = response.body?.getReader();
778+
if (!reader) {
779+
throw new Error('No response body reader available');
780+
}
781+
782+
const decoder = new TextDecoder();
783+
let buffer = '';
784+
let currentEvent = '';
785+
786+
try {
787+
while (true) {
788+
const { done, value } = await reader.read();
789+
if (done) break;
790+
791+
const chunk = decoder.decode(value, { stream: true });
792+
793+
buffer += chunk;
794+
const lines = buffer.split('\n');
795+
796+
buffer = lines.pop() || '';
797+
798+
for (const line of lines) {
799+
if (line.startsWith('event:')) {
800+
currentEvent = line.substring(6).trim();
801+
}
802+
else if (line.startsWith('data:')) {
803+
const data = line.substring(5).trim();
804+
805+
if (data) {
806+
try {
807+
const parsedData = JSON.parse(data);
808+
onEvent?.(currentEvent, parsedData);
809+
} catch (parseError) {
810+
if (currentEvent === 'connection' && data === 'connected') {
811+
onEvent?.(currentEvent, { status: 'connected', message: data });
812+
} else {
813+
onEvent?.(currentEvent, data);
814+
}
815+
}
816+
}
817+
}
818+
else if (line.trim() === '') {
819+
currentEvent = '';
820+
}
821+
}
822+
}
823+
} catch (streamError: any) {
824+
ctx.log.debug('SSE Streaming error:', streamError);
825+
onEvent?.('error', streamError);
826+
} finally {
827+
reader.releaseLock();
828+
}
829+
830+
} catch (error) {
831+
ctx.log.debug('SSE Connection error:', error);
832+
onEvent?.('error', error);
833+
}
834+
835+
return {
836+
abort: () => abortController.abort()
837+
};
838+
}
839+
840+
export async function startSSEListener(ctx: Context) {
841+
let currentConnection: { abort: () => void } | null = null;
842+
let errorCount = 0;
843+
844+
try {
845+
ctx.log.debug('Attempting SSE connection');
846+
const accessKey = ctx.env.LT_ACCESS_KEY;
847+
const username = ctx.env.LT_USERNAME;
848+
849+
const basicAuthToken = createBasicAuthToken(username, accessKey);
850+
ctx.log.debug(`Basic auth token: ${basicAuthToken}`);
851+
currentConnection = await listenToSmartUISSE(
852+
ctx.env.SMARTUI_SSE_URL,
853+
basicAuthToken,
854+
ctx,
855+
(eventType, data) => {
856+
switch (eventType) {
857+
case 'open':
858+
ctx.log.debug('Connected to SSE server');
859+
break;
860+
861+
case 'connection':
862+
ctx.log.debug('Connection confirmed:', data);
863+
break;
864+
865+
case 'Dot_buildCompleted':
866+
ctx.log.debug('Build completed');
867+
ctx.log.info(chalk.green.bold('Build completed'));
868+
process.exit(0);
869+
case 'DOTUIError':
870+
if (data.buildId== ctx.build.id) {
871+
errorCount++;
872+
ctx.log.info(chalk.red.bold(`Error: ${data.message}`));
873+
}
874+
break;
875+
case 'DOTUIWarning':
876+
if (data.buildId== ctx.build.id) {
877+
ctx.log.info(chalk.yellow.bold(`Warning: ${data.message}`));
878+
}
879+
break;
880+
case 'error':
881+
ctx.log.debug('SSE Error occurred:', data);
882+
currentConnection?.abort();
883+
return;
884+
}
885+
}
886+
);
887+
888+
} catch (error) {
889+
ctx.log.debug('Failed to start SSE listener:', error);
890+
}
743891
}

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.options.showRenderErrors||ctx.config.showRenderErrors) && ctx.build && ctx.build.id) {
20+
if(ctx.env.LT_USERNAME&&ctx.env.LT_ACCESS_KEY) {
21+
startSSEListener(ctx);
22+
} else {
23+
ctx.log.info('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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface Context {
4343
loadDomContent?: boolean;
4444
approvalThreshold?: number;
4545
rejectionThreshold?: number;
46+
showRenderErrors?: boolean
4647
};
4748
uploadFilePath: string;
4849
webStaticConfig: WebStaticConfig;
@@ -71,7 +72,8 @@ export interface Context {
7172
fetchResultsFileName?: string,
7273
baselineBranch?: string,
7374
baselineBuild?: string,
74-
githubURL?: string
75+
githubURL?: string,
76+
showRenderErrors?: boolean
7577
}
7678
cliVersion: string;
7779
totalSnapshots: number;
@@ -120,6 +122,8 @@ export interface Env {
120122
SMARTUI_API_SKIP_CERTIFICATES: boolean;
121123
USE_REMOTE_DISCOVERY: boolean;
122124
SMART_GIT: boolean;
125+
SHOW_RENDER_ERRORS: boolean;
126+
SMARTUI_SSE_URL: string;
123127
}
124128

125129
export interface Snapshot {

0 commit comments

Comments
 (0)