Skip to content

Commit bdf170e

Browse files
committed
handle tunnel cases with exec:start
1 parent 0993b97 commit bdf170e

File tree

9 files changed

+91
-48
lines changed

9 files changed

+91
-48
lines changed

src/commander/exec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ command
4040
ctx.args.execCommand = execCommand
4141
ctx.snapshotQueue = new snapshotQueue(ctx)
4242
ctx.totalSnapshots = 0
43+
ctx.sourceCommand = 'exec'
4344

4445
let tasks = new Listr<Context>(
4546
[

src/commander/server.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import createBuildExec from '../tasks/createBuildExec.js';
99
import snapshotQueue from '../lib/snapshotQueue.js';
1010
import { startPolling, startPingPolling } from '../lib/utils.js';
1111
import startTunnel from '../tasks/startTunnel.js'
12+
const util = require('util');
1213

1314
const command = new Command();
1415

@@ -28,6 +29,7 @@ command
2829
ctx.snapshotQueue = new snapshotQueue(ctx);
2930
ctx.totalSnapshots = 0
3031
ctx.isStartExec = true
32+
ctx.sourceCommand = 'exec-start'
3133

3234
let tasks = new Listr<Context>(
3335
[
@@ -53,13 +55,27 @@ command
5355
try {
5456
await tasks.run(ctx);
5557
if (ctx.build && ctx.build.id) {
56-
startPingPolling(ctx, 'exec-start');
58+
startPingPolling(ctx);
5759
}
5860
if (ctx.options.fetchResults && ctx.build && ctx.build.id) {
5961
startPolling(ctx, '', false, '')
6062
}
63+
64+
// await ctx.client.getScreenshotData("567890", false, ctx.log, "755#a5ac6a67-289a-427d-b004-7dfff6c3484b#fanniemae-stage", 'smartui-bbf5b47005');
65+
6166

6267
} catch (error) {
68+
// Log the error in a human-readable format
69+
// ctx.log.debug(util.inspect(error, { showHidden: false, depth: null }));
70+
71+
// console.log(`Json Error: ${JSON.stringify(error, null, 2)}`);
72+
// if (error?.message.includes('ENOTFOUND')) {
73+
// ctx.log.error('Error: Network error occurred while fetching build status while polling. Please check your connection and try again.');
74+
// } else {
75+
// // Log the error in a human-readable format
76+
// ctx.log.debug(util.inspect(error, { showHidden: false, depth: null }));
77+
// ctx.log.error(`Error fetching build status while polling: ${JSON.stringify(error)}`);
78+
// }
6379
console.error('Error during server execution:', error);
6480
process.exit(1);
6581
}

src/lib/httpClient.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import FormData from 'form-data';
33
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
44
import { Env, Snapshot, ProcessedSnapshot, Git, Build, Context, DiscoveryErrors } from '../types.js';
55
import constants from './constants.js';
6-
import type { Logger } from 'winston'
6+
import { type Logger } from 'winston'
77
import pkgJSON from './../../package.json'
88
import https from 'https';
99

@@ -77,7 +77,7 @@ export default class httpClient {
7777
(response) => response,
7878
async (error) => {
7979
const { config } = error;
80-
if (config && config.url === '/screenshot' && config.method === 'post') {
80+
if (config && config.url === '/screenshot' && config.method === 'post' && error?.response?.status !== 401) {
8181
// Set default retry count and delay if not already defined
8282
if (!config.retryCount) {
8383
config.retryCount = 0;
@@ -242,11 +242,12 @@ export default class httpClient {
242242
}, log)
243243
}
244244

245-
getScreenshotData(buildId: string, baseline: boolean, log: Logger, projectToken: string) {
245+
getScreenshotData(buildId: string, baseline: boolean, log: Logger, projectToken: string, buildName: string) {
246+
log.debug(`Fetching screenshot data for buildId: ${buildId} having buildName: ${buildName} with baseline: ${baseline}`);
246247
return this.request({
247248
url: '/screenshot',
248249
method: 'GET',
249-
params: { buildId, baseline },
250+
params: { buildId, baseline, buildName },
250251
headers: {projectToken: projectToken}
251252
}, log);
252253
}

src/lib/server.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import fastify, { FastifyInstance, RouteShorthandOptions } from 'fastify';
44
import { readFileSync, truncate } from 'fs'
55
import { Context } from '../types.js'
66
import { validateSnapshot } from './schemaValidation.js'
7-
import { pingIntervalId } from './utils.js';
8-
import { stopTunnelHelper } from './utils.js';
7+
import { pingIntervalId, startPollingForTunnel, stopTunnelHelper, isTunnelPolling } from './utils.js';
98

109
const uploadDomToS3ViaEnv = process.env.USE_LAMBDA_INTERNAL || false;
1110
export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMessage, ServerResponse>> => {
@@ -106,6 +105,7 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
106105
let replyCode: number;
107106
let replyBody: Record<string, any>;
108107
try {
108+
ctx.log.info('Received stop command. Finalizing build ...');
109109
if(ctx.config.delayedUpload){
110110
ctx.log.debug("started after processing because of delayedUpload")
111111
ctx.snapshotQueue?.startProcessingfunc()
@@ -118,6 +118,7 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
118118
}
119119
}, 1000);
120120
})
121+
let buildUrls = `build url: ${ctx.build.url}\n`;
121122

122123
for (const [sessionId, capabilities] of ctx.sessionCapabilitiesMap.entries()) {
123124
try {
@@ -126,9 +127,12 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
126127
const totalSnapshots = capabilities?.snapshotCount || 0;
127128
const sessionBuildUrl = capabilities?.buildURL || '';
128129
const testId = capabilities?.id || '';
129-
130+
ctx.log.debug(`Capabilities for sessionId ${sessionId}: ${JSON.stringify(capabilities)}`)
130131
if (buildId && projectToken) {
131132
await ctx.client.finalizeBuildForCapsWithToken(buildId, totalSnapshots, projectToken, ctx.log);
133+
if (ctx.autoTunnelStarted) {
134+
await startPollingForTunnel(ctx, buildId, false, projectToken, capabilities?.buildName);
135+
}
132136
}
133137

134138
if (testId && buildId) {
@@ -151,10 +155,15 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
151155
}
152156
}
153157

154-
//Handle Tunnel closure
155-
if (ctx.config.tunnel && ctx.config.tunnel?.type === 'auto') {
156-
await stopTunnelHelper(ctx)
157-
}
158+
159+
//If Tunnel Details are present, start polling for tunnel status
160+
if (ctx.tunnelDetails && ctx.tunnelDetails.tunnelHost != "" && ctx.build?.id) {
161+
await startPollingForTunnel(ctx, ctx.build.id, false, '', '');
162+
}
163+
//stop the tunnel if it was auto started and no tunnel polling is active
164+
if (ctx.autoTunnelStarted && isTunnelPolling === null) {
165+
await stopTunnelHelper(ctx);
166+
}
158167

159168
await ctx.browser?.close();
160169
if (ctx.server){
@@ -174,6 +183,8 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
174183
replyBody = { error: { message: error.message } };
175184
}
176185

186+
ctx.log.info('Stop command processed. Tearing down server.');
187+
177188
// Step 5: Return the response
178189
return reply.code(replyCode).send(replyBody);
179190
});

src/lib/utils.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import fs from 'fs';
77
import { globalAgent } from 'http';
88
import { promisify } from 'util'
99
import { build } from 'tsup';
10+
const util = require('util'); // Import the util module
1011

1112
var lambdaTunnel = require('@lambdatest/node-tunnel');
1213
const sleep = promisify(setTimeout);
@@ -248,9 +249,9 @@ export async function startPolling(ctx: Context, build_id: string, baseline: boo
248249
try {
249250
let resp;
250251
if (build_id) {
251-
resp = await ctx.client.getScreenshotData(build_id, baseline, ctx.log, projectToken);
252+
resp = await ctx.client.getScreenshotData(build_id, baseline, ctx.log, projectToken, '');
252253
} else if (ctx.build && ctx.build.id) {
253-
resp = await ctx.client.getScreenshotData(ctx.build.id, ctx.build.baseline, ctx.log, '');
254+
resp = await ctx.client.getScreenshotData(ctx.build.id, ctx.build.baseline, ctx.log, '', '');
254255
} else {
255256
return;
256257
}
@@ -324,7 +325,7 @@ export async function startPolling(ctx: Context, build_id: string, baseline: boo
324325

325326
export let pingIntervalId: NodeJS.Timeout | null = null;
326327

327-
export async function startPingPolling(ctx: Context, event: string): Promise<void> {
328+
export async function startPingPolling(ctx: Context): Promise<void> {
328329
try {
329330
ctx.log.debug('Sending initial ping to server...');
330331
await ctx.client.ping(ctx.build.id, ctx.log);
@@ -333,12 +334,13 @@ export async function startPingPolling(ctx: Context, event: string): Promise<voi
333334
ctx.log.error(`Error during initial ping: ${error.message}`);
334335
}
335336

337+
let sourceCommand = ctx.sourceCommand? ctx.sourceCommand : '';
336338
// Start the polling interval
337339
pingIntervalId = setInterval(async () => {
338340
try {
339-
ctx.log.debug('Sending ping to server...'+ event);
341+
ctx.log.debug('Sending ping to server... '+ sourceCommand);
340342
await ctx.client.ping(ctx.build.id, ctx.log);
341-
ctx.log.debug('Ping sent successfully.'+ event);
343+
ctx.log.debug('Ping sent successfully. '+ sourceCommand);
342344
} catch (error: any) {
343345
ctx.log.error(`Error during ping polling: ${error.message}`);
344346
}
@@ -406,54 +408,56 @@ export async function startTunnelBinary(ctx: Context) {
406408
}
407409
}
408410

409-
export async function startPollingForTunnel(ctx: Context, build_id: string, baseline: boolean, projectToken: string): Promise<void> {
411+
export let isTunnelPolling: NodeJS.Timeout | null = null;
412+
413+
export async function startPollingForTunnel(ctx: Context, build_id: string, baseline: boolean, projectToken: string, buildName: string): Promise<void> {
414+
if (isTunnelPolling) {
415+
ctx.log.debug('Tunnel polling is already active. Skipping for build_id: ' + build_id);
416+
return;
417+
}
410418
const intervalId = setInterval(async () => {
411419
try {
412420
let resp;
413421
if (build_id) {
414-
resp = await ctx.client.getScreenshotData(build_id, baseline, ctx.log, projectToken);
422+
resp = await ctx.client.getScreenshotData(build_id, baseline, ctx.log, projectToken, buildName);
415423
} else if (ctx.build && ctx.build.id) {
416-
resp = await ctx.client.getScreenshotData(ctx.build.id, ctx.build.baseline, ctx.log, '');
424+
resp = await ctx.client.getScreenshotData(ctx.build.id, ctx.build.baseline, ctx.log, '', '');
417425
} else {
426+
ctx.log.debug('No build information available for polling tunnel status.');
427+
clearInterval(intervalId);
428+
await stopTunnelHelper(ctx);
418429
return;
419430
}
420-
431+
ctx.log.debug(' resp from polling for tunnel status: ' + JSON.stringify(resp));
421432
if (!resp.build) {
422433
ctx.log.info("Error: Build data is null.");
423434
clearInterval(intervalId);
424-
425-
const tunnelRunningStatus = await tunnelInstance.isRunning();
426-
ctx.log.debug('Running status of tunnel before stopping ? ' + tunnelRunningStatus);
427-
428-
const status = await tunnelInstance.stop();
429-
ctx.log.debug('Tunnel is Stopped ? ' + status);
430-
435+
await stopTunnelHelper(ctx);
431436
return;
432437
}
433438

434439
if (resp.build.build_status_ind === constants.BUILD_COMPLETE || resp.build.build_status_ind === constants.BUILD_ERROR) {
435440
clearInterval(intervalId);
436-
437-
const tunnelRunningStatus = await tunnelInstance.isRunning();
438-
ctx.log.debug('Running status of tunnel before stopping ? ' + tunnelRunningStatus);
439-
440-
const status = await tunnelInstance.stop();
441-
ctx.log.debug('Tunnel is Stopped ? ' + status);
441+
await stopTunnelHelper(ctx);
442442
return;
443443
}
444444
} catch (error: any) {
445-
if (error.message.includes('ENOTFOUND')) {
445+
if (error?.message.includes('ENOTFOUND')) {
446446
ctx.log.error('Error: Network error occurred while fetching build status while polling. Please check your connection and try again.');
447447
clearInterval(intervalId);
448448
} else {
449-
ctx.log.error(`Error fetching build status while polling: ${error.message}`);
449+
// Log the error in a human-readable format
450+
ctx.log.debug(util.inspect(error, { showHidden: false, depth: null }));
451+
ctx.log.error(`Error fetching build status while polling: ${JSON.stringify(error)}`);
450452
}
451453
clearInterval(intervalId);
452454
}
453455
}, 5000);
456+
isTunnelPolling = intervalId;
454457
}
455458

456459
export async function stopTunnelHelper(ctx: Context) {
460+
ctx.log.debug('stop-tunnel:: Stopping the tunnel now');
457461
const tunnelRunningStatus = await tunnelInstance.isRunning();
458462
ctx.log.debug('stop-tunnel:: Running status of tunnel before stopping ? ' + tunnelRunningStatus);
459463

src/tasks/createBuildExec.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ListrTask, ListrRendererFactory } from 'listr2';
22
import { Context } from '../types.js'
33
import chalk from 'chalk';
44
import { updateLogContext } from '../lib/logger.js';
5-
import { startTunnelBinary, startPollingForTunnel, stopTunnelHelper, startPingPolling } from '../lib/utils.js';
5+
import { stopTunnelHelper, startPingPolling } from '../lib/utils.js';
66

77
export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRendererFactory> => {
88
return {
@@ -42,16 +42,11 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
4242
} else {
4343
task.output = chalk.gray(`Empty PROJECT_TOKEN and PROJECT_NAME. Skipping Creation of Build!`)
4444
task.title = 'Skipped SmartUI build creation'
45-
if (ctx.config.tunnel && ctx.config.tunnel?.type === 'auto') {
46-
await stopTunnelHelper(ctx)
47-
}
4845
}
4946

5047
if (ctx.config.tunnel && ctx.config.tunnel?.type === 'auto') {
51-
if (ctx.build && ctx.build.id) {
52-
startPollingForTunnel(ctx, '', false, '');
53-
} else {
54-
startPingPolling(ctx, "tunnel-process");
48+
if (ctx.build && ctx.build.id && ctx.sourceCommand != "exec-start") {
49+
startPingPolling(ctx);
5550
}
5651
}
5752

@@ -69,7 +64,7 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
6964
if (process.env.USE_REMOTE_DISCOVERY === undefined) {
7065
ctx.env.USE_REMOTE_DISCOVERY = true;
7166
process.env.USE_REMOTE_DISCOVERY = 'true';
72-
task.output += chalk.gray(`\n Using remote discovery by deafult for this build`);
67+
task.output += chalk.gray(`\n Using remote discovery by default for this build`);
7368
}
7469
ctx.log.debug(`USE_REMOTE_DISCOVERY is set to ${ctx.env.USE_REMOTE_DISCOVERY}`);
7570

src/tasks/finalizeBuild.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ListrTask, ListrRendererFactory } from 'listr2';
22
import { Context } from '../types.js'
33
import { updateLogContext } from '../lib/logger.js';
44
import chalk from 'chalk';
5-
import { startPolling, pingIntervalId } from '../lib/utils.js';
5+
import { startPolling, pingIntervalId, startPollingForTunnel, stopTunnelHelper, isTunnelPolling } from '../lib/utils.js';
66
import { unlinkSync } from 'fs';
77
import constants from '../lib/constants.js';
88
import fs from 'fs';
@@ -32,7 +32,7 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
3232
clearInterval(pingIntervalId);
3333
ctx.log.debug('Ping polling stopped immediately from Finalize Build');
3434
}
35-
35+
3636
for (const [sessionId, capabilities] of ctx.sessionCapabilitiesMap.entries()) {
3737
try {
3838
const buildId = capabilities?.buildId || '';
@@ -54,9 +54,12 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
5454
ctx.fetchResultsForBuild.push(buildId);
5555
}
5656
}
57-
57+
ctx.log.debug(`Capabilities for sessionId ${sessionId}: ${JSON.stringify(capabilities)}`)
5858
if (buildId && projectToken) {
5959
await ctx.client.finalizeBuildForCapsWithToken(buildId, totalSnapshots, projectToken, ctx.log);
60+
if (ctx.autoTunnelStarted) {
61+
await startPollingForTunnel(ctx, buildId, false, projectToken, capabilities?.buildName);
62+
}
6063
}
6164

6265
if (testId && buildId) {
@@ -68,6 +71,15 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
6871
}
6972
task.output = chalk.gray(buildUrls);
7073
task.title = 'Finalized build';
74+
75+
//If Tunnel Details are present, start polling for tunnel status
76+
if (ctx.tunnelDetails && ctx.tunnelDetails.tunnelHost != "" && ctx.build?.id) {
77+
await startPollingForTunnel(ctx, ctx.build.id, false, '', '');
78+
}
79+
//stop the tunnel if it was auto started and no tunnel polling is active
80+
if (ctx.autoTunnelStarted && isTunnelPolling === null) {
81+
await stopTunnelHelper(ctx);
82+
}
7183

7284
// cleanup and upload logs
7385
try {

src/tasks/startTunnel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
1313
try {
1414
await startTunnelBinary(ctx);
1515
ctx.isStartExec = true;
16+
ctx.autoTunnelStarted = true;
1617
task.title = 'Tunnel Started';
1718
task.output = chalk.gray('Tunnel started successfully');
1819
} catch (error: any) {

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ export interface Context {
9090
mergeByBranch?: boolean;
9191
mergeByBuild?: boolean;
9292
contextToSnapshotMap?: Map<string, number>;
93+
sourceCommand?: string;
94+
autoTunnelStarted?: boolean;
9395
}
9496

9597
export interface Env {

0 commit comments

Comments
 (0)