Skip to content

Commit d97b70a

Browse files
smartui caps initial commit
1 parent 8b6e153 commit d97b70a

File tree

9 files changed

+195
-25
lines changed

9 files changed

+195
-25
lines changed

src/lib/ctx.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,9 @@ export default (options: Record<string, string>): Context => {
132132
totalSnapshots: -1,
133133
isStartExec: false,
134134
isSnapshotCaptured: false
135+
sessionCapabilitiesMap: new Map<string, any[]>(),
136+
sessionToBuildMap: new Map<string, string>(),
137+
buildToSnapshotCountMap: new Map<string, number>() ,
138+
buildToProjectTokenMap: new Map<string, string>()
135139
}
136140
}

src/lib/httpClient.ts

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,29 @@ export default class httpClient {
174174
}
175175

176176

177+
getSmartUICapabilities(sessionId: string, config: any, git: any, log: Logger) {
178+
console.log(`get smartui caps api called with sessionId: ${sessionId}`);
179+
const serializedConfig = JSON.stringify(config);
180+
const serializedGit = JSON.stringify(git);
181+
console.log(`Serialized Config: ${serializedConfig}`);
182+
console.log(`Serialized Git: ${serializedGit}`);
183+
return this.request({
184+
url: '/sessions/capabilities',
185+
method: 'GET',
186+
params: {
187+
sessionId: sessionId,
188+
config: serializedConfig,
189+
git: serializedGit,
190+
},
191+
headers: {
192+
projectToken: '',
193+
projectName: '',
194+
username: '',
195+
accessKey: ''
196+
},
197+
}, log);
198+
}
199+
177200
finalizeBuild(buildId: string, totalSnapshots: number, log: Logger) {
178201
let params: Record<string, string | number> = { buildId };
179202
if (totalSnapshots > -1) params.totalSnapshots = totalSnapshots;
@@ -185,9 +208,33 @@ export default class httpClient {
185208
}, log)
186209
}
187210

188-
uploadSnapshot(ctx: Context, snapshot: ProcessedSnapshot) {
211+
async finalizeBuildForCapsWithToken(buildId: string, totalSnapshots: number, projectToken: string, log: Logger): Promise<void> {
212+
try {
213+
let params: Record<string, string | number> = { buildId };
214+
if (totalSnapshots > -1) params.totalSnapshots = totalSnapshots;
215+
216+
await this.request({
217+
url: '/build',
218+
method: 'DELETE',
219+
params: params,
220+
headers: {
221+
projectToken: projectToken, // Use projectToken dynamically
222+
},
223+
}, log);
224+
225+
log.debug(`Successfully finalized build ${buildId} with ${totalSnapshots} snapshots and projectToken ${projectToken}`);
226+
} catch (error: any) {
227+
log.debug(`Failed to finalize build ${buildId}: ${error.message}`);
228+
throw error; // Re-throw error for further handling if necessary
229+
}
230+
}
231+
232+
233+
uploadSnapshot(ctx: Context, snapshot: ProcessedSnapshot, capsBuildId: string) {
234+
// Use capsBuildId if provided, otherwise fallback to ctx.build.id
235+
const buildId = capsBuildId !== '' ? capsBuildId : ctx.build.id;
189236
return this.request({
190-
url: `/builds/${ctx.build.id}/snapshot`,
237+
url: `/builds/${buildId}/snapshot`,
191238
method: 'POST',
192239
headers: { 'Content-Type': 'application/json' },
193240
data: {
@@ -197,7 +244,7 @@ export default class httpClient {
197244
source: 'cli'
198245
}
199246
}
200-
}, ctx.log)
247+
}, ctx.log);
201248
}
202249

203250
processSnapshot(ctx: Context, snapshot: ProcessedSnapshot, snapshotUuid: string) {
@@ -217,6 +264,28 @@ export default class httpClient {
217264
}
218265
}, ctx.log)
219266
}
267+
uploadSnapshotForCaps(ctx: Context, snapshot: ProcessedSnapshot, capsBuildId: string, capsProjectToken: string) {
268+
// Use capsBuildId if provided, otherwise fallback to ctx.build.id
269+
const buildId = capsBuildId !== '' ? capsBuildId : ctx.build.id;
270+
271+
return this.request({
272+
url: `/builds/${buildId}/snapshot`,
273+
method: 'POST',
274+
headers: {
275+
'Content-Type': 'application/json',
276+
projectToken: capsProjectToken !== '' ? capsProjectToken : this.projectToken // Use capsProjectToken dynamically
277+
},
278+
data: {
279+
snapshot,
280+
test: {
281+
type: ctx.testType,
282+
source: 'cli'
283+
}
284+
}
285+
}, ctx.log);
286+
}
287+
288+
220289

221290
uploadScreenshot(
222291
{ id: buildId, name: buildName, baseline }: Build,

src/lib/schemaValidation.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,10 @@ const SnapshotSchema: JSONSchemaType<Snapshot> = {
390390
loadDomContent: {
391391
type: "boolean",
392392
errorMessage: "Invalid snapshot options; loadDomContent must be a boolean"
393+
},
394+
sessionId: {
395+
type: "string",
396+
errorMessage: "Invalid snapshot options; sessionId must be a string"
393397
}
394398
},
395399
additionalProperties: false

src/lib/server.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,40 @@ export default async (ctx: Context): Promise<FastifyInstance<Server, IncomingMes
3636
try {
3737
let { snapshot, testType } = request.body;
3838
if (!validateSnapshot(snapshot)) throw new Error(validateSnapshot.errors[0].message);
39+
40+
// Fetch sessionId from snapshot options if present
41+
const sessionId = snapshot?.options?.sessionId;
42+
let capsBuildId = ''
43+
44+
if (sessionId) {
45+
// Check if sessionId exists in the map
46+
if (ctx.sessionCapabilitiesMap?.has(sessionId)) {
47+
// Use cached capabilities if available
48+
const cachedCapabilities = ctx.sessionCapabilitiesMap.get(sessionId);
49+
capsBuildId = cachedCapabilities?.buildId || ''
50+
if (capsBuildId) {
51+
ctx.sessionToBuildMap.set(sessionId, capsBuildId);
52+
ctx.buildToProjectTokenMap.set(capsBuildId, cachedCapabilities?.projectToken || '');
53+
}
54+
} else {
55+
// If not cached, fetch from API and cache it
56+
try {
57+
let fetchedCapabilitiesResp = await ctx.client.getSmartUICapabilities(sessionId, ctx.config, ctx.git, ctx.log);
58+
ctx.sessionCapabilitiesMap.set(sessionId, fetchedCapabilitiesResp);
59+
capsBuildId = fetchedCapabilitiesResp?.buildId || ''
60+
console.log(JSON.stringify(fetchedCapabilitiesResp))
61+
62+
if (capsBuildId) {
63+
ctx.sessionToBuildMap.set(sessionId, capsBuildId);
64+
ctx.buildToProjectTokenMap.set(capsBuildId, fetchedCapabilitiesResp.data?.projectToken || '');
65+
}
66+
} catch (error: any) {
67+
console.log(`Failed to fetch capabilities for sessionId ${sessionId}: ${error.message}`);
68+
}
69+
}
70+
}
71+
console.log("I have success in retrive")
72+
3973
ctx.testType = testType;
4074
ctx.snapshotQueue?.enqueue(snapshot);
4175
ctx.isSnapshotCaptured = true;

src/lib/snapshotQueue.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { access } from "fs";
12
import { Snapshot, Context} from "../types.js";
23
import constants from "./constants.js";
34
import processSnapshot from "./processSnapshot.js"
@@ -292,6 +293,21 @@ export default class Queue {
292293
}
293294

294295
if (!drop) {
296+
// Fetch sessionId from snapshot options if present
297+
const sessionId = snapshot?.options?.sessionId;
298+
let capsBuildId = ''
299+
let capsProjectToken = '';
300+
let useCapsBuildId = false
301+
302+
// Fetch projectToken and buildId if sessionId exists in sessionCapabilitiesMap
303+
if (sessionId && this.ctx.sessionCapabilitiesMap?.has(sessionId)) {
304+
const cachedCapabilities = this.ctx.sessionCapabilitiesMap.get(sessionId);
305+
capsProjectToken = cachedCapabilities?.projectToken || '';
306+
capsBuildId = cachedCapabilities?.buildId || '';
307+
useCapsBuildId = !!capsBuildId; // Set to true if capsBuildId is not empty
308+
}
309+
310+
// Process and upload snapshot
295311
let { processedSnapshot, warnings } = await processSnapshot(snapshot, this.ctx);
296312

297313
if(this.ctx.build && this.ctx.build.useKafkaFlow) {
@@ -306,8 +322,20 @@ export default class Queue {
306322
}
307323

308324
this.ctx.totalSnapshots++;
325+
if (useCapsBuildId) {
326+
await this.ctx.client.uploadSnapshotForCaps(this.ctx, processedSnapshot, capsBuildId, capsProjectToken);
327+
// Increment snapshot count for the specific buildId
328+
const currentCount = this.ctx.buildToSnapshotCountMap.get(capsBuildId) || 0;
329+
this.ctx.buildToSnapshotCountMap.set(capsBuildId, currentCount + 1);
330+
} else {
331+
await this.ctx.client.uploadSnapshot(this.ctx, processedSnapshot, capsBuildId);
332+
// Increment global totalSnapshots
333+
this.ctx.totalSnapshots++;
334+
}
335+
309336
this.processedSnapshots.push({ name: snapshot.name, warnings });
310337
}
338+
311339
} catch (error: any) {
312340
this.ctx.log.debug(`snapshot failed; ${error}`);
313341
this.processedSnapshots.push({ name: snapshot.name, error: error.message });

src/tasks/auth.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,22 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
1010
updateLogContext({task: 'auth'});
1111

1212
try {
13-
const authResult = await ctx.client.auth(ctx.log, ctx.env);
14-
if (authResult === 2) {
15-
task.output = chalk.gray(`New project '${ctx.env.PROJECT_NAME}' created successfully`);
16-
} else if (authResult === 0) {
17-
task.output = chalk.gray(`Using existing project token '******#${ctx.env.PROJECT_TOKEN.split('#').pop()}'`);
18-
} else if (authResult === 1) {
19-
task.output = chalk.gray(`Using existing project '${ctx.env.PROJECT_NAME}'`);
13+
if ( !ctx.env.PROJECT_NAME && !ctx.env.PROJECT_TOKEN ) {
14+
ctx.authenticatedInitially = false
15+
task.output = chalk.gray(`Empty PROJECT_TOKEN and PROJECT_NAME. Skipping authentication. Expecting SmartUI Capabilities in driver!`)
16+
task.title = 'Skipped Authentication with SmartUI';
17+
} else {
18+
const authResult = await ctx.client.auth(ctx.log, ctx.env);
19+
if (authResult === 2) {
20+
task.output = chalk.gray(`New project '${ctx.env.PROJECT_NAME}' created successfully`);
21+
} else if (authResult === 0) {
22+
task.output = chalk.gray(`Using existing project token '******#${ctx.env.PROJECT_TOKEN.split('#').pop()}'`);
23+
} else if (authResult === 1) {
24+
task.output = chalk.gray(`Using existing project '${ctx.env.PROJECT_NAME}'`);
25+
}
26+
ctx.authenticatedInitially = true
27+
task.title = 'Authenticated with SmartUI';
2028
}
21-
task.title = 'Authenticated with SmartUI';
2229
} catch (error: any) {
2330
ctx.log.debug(error);
2431
task.output = chalk.gray(error.message);

src/tasks/createBuild.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
1010
updateLogContext({task: 'createBuild'});
1111

1212
try {
13-
let resp = await ctx.client.createBuild(ctx.git, ctx.config, ctx.log, ctx.build.name, ctx.isStartExec);
14-
ctx.build = {
15-
id: resp.data.buildId,
16-
name: resp.data.buildName,
17-
url: resp.data.buildURL,
18-
baseline: resp.data.baseline,
19-
useKafkaFlow: resp.data.useKafkaFlow || false,
13+
if (ctx.authenticatedInitially) {
14+
let resp = await ctx.client.createBuild(ctx.git, ctx.config, ctx.log, ctx.build.name, ctx.isStartExec);
15+
ctx.build = {
16+
id: resp.data.buildId,
17+
name: resp.data.buildName,
18+
url: resp.data.buildURL,
19+
baseline: resp.data.baseline,
20+
}
21+
task.output = chalk.gray(`build id: ${resp.data.buildId}`);
22+
task.title = 'SmartUI build created'
23+
} else {
24+
task.output = chalk.gray(`Empty PROJECT_TOKEN and PROJECT_NAME. Skipping Creation of Build!`)
25+
task.title = 'Skipped SmartUI build creation'
2026
}
21-
22-
task.output = chalk.gray(`build id: ${resp.data.buildId}`);
23-
task.title = 'SmartUI build created'
2427
} catch (error: any) {
2528
ctx.log.debug(error);
2629
task.output = chalk.gray(error.message);

src/tasks/finalizeBuild.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,30 @@ export default (ctx: Context): ListrTask<Context, ListrRendererFactory, ListrRen
1313
updateLogContext({task: 'finalizeBuild'});
1414

1515
try {
16-
await ctx.client.finalizeBuild(ctx.build.id, ctx.totalSnapshots, ctx.log);
17-
task.output = chalk.gray(`build url: ${ctx.build.url}`);
18-
task.title = 'Finalized build';
16+
if (ctx.build.id) {
17+
await ctx.client.finalizeBuild(ctx.build.id, ctx.totalSnapshots, ctx.log);
18+
}
1919
} catch (error: any) {
2020
ctx.log.debug(error);
2121
task.output = chalk.gray(error.message);
2222
throw new Error('Finalize build failed');
2323
}
2424

25+
if (ctx.buildToSnapshotCountMap) {
26+
for (const [buildId, totalSnapshots] of ctx.buildToSnapshotCountMap.entries()) {
27+
try {
28+
// Fetch projectToken from buildToProjectTokenMap
29+
const projectToken = ctx.buildToProjectTokenMap?.get(buildId) || '';
30+
await ctx.client.finalizeBuildForCapsWithToken(buildId, totalSnapshots, projectToken, ctx.log);
31+
} catch (error: any) {
32+
ctx.log.debug(`Error finalizing build ${buildId}: ${error.message}`);
33+
}
34+
}
35+
}
36+
37+
task.output = chalk.gray(`build url: ${ctx.build.url}`);
38+
task.title = 'Finalized build';
39+
2540
// cleanup and upload logs
2641
try {
2742
await ctx.browser?.close();

src/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface Context {
1313
server?: FastifyInstance<Server, IncomingMessage, ServerResponse>;
1414
client: httpClient;
1515
browser?: Browser;
16+
authenticatedInitially?: boolean;
1617
snapshotQueue?: snapshotQueue;
1718
config: {
1819
web?: WebConfig;
@@ -57,6 +58,10 @@ export interface Context {
5758
testType?: string;
5859
isStartExec ?: boolean;
5960
isSnapshotCaptured ?: boolean;
61+
sessionCapabilitiesMap?: Map<string, any[]>;
62+
buildToSnapshotCountMap?: Map<string, number>;
63+
sessionToBuildMap?: Map<string, string>;
64+
buildToProjectTokenMap?: Map<string, string>;
6065
}
6166

6267
export interface Env {
@@ -113,7 +118,8 @@ export interface Snapshot {
113118
orientation?: string
114119
},
115120
loadDomContent?: boolean;
116-
ignoreType?: string[]
121+
ignoreType?: string[],
122+
sessionId?: string
117123
}
118124
}
119125

0 commit comments

Comments
 (0)