Skip to content

Commit df391e4

Browse files
committed
✨ Add test site, pass build ID to check-shas endpoint
1 parent fd981e1 commit df391e4

File tree

15 files changed

+2477
-98
lines changed

15 files changed

+2477
-98
lines changed

.github/workflows/ci.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,41 @@ jobs:
5454

5555
- name: Build
5656
run: npm run build
57+
58+
test-site:
59+
runs-on: ubuntu-latest
60+
needs: lint
61+
62+
steps:
63+
- uses: actions/checkout@v4
64+
65+
- name: Use Node.js 22
66+
uses: actions/setup-node@v4
67+
with:
68+
node-version: 22
69+
cache: 'npm'
70+
71+
- name: Install CLI dependencies
72+
run: npm ci
73+
74+
- name: Build CLI
75+
run: npm run build
76+
77+
- name: Install test site dependencies
78+
working-directory: ./test-site
79+
run: npm ci
80+
81+
- name: Install Playwright browsers
82+
working-directory: ./test-site
83+
run: npx playwright install --with-deps
84+
85+
- name: Run test site Playwright tests
86+
working-directory: ./test-site
87+
run: npm test
88+
89+
- name: Test Vizzly integration
90+
working-directory: ./test-site
91+
run: vizzly run "npm test" --build-name "CI Test Run - ${{ github.sha }}" --wait
92+
env:
93+
CI: true
94+
VIZZLY_TOKEN: ${{ secrets.VIZZLY_TOKEN }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ Thumbs.db
3030

3131
.vizzly
3232
tests/client/
33+
test-site/playwright-report/
34+
test-site/test-results/

.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,8 @@ node_modules/
8787
# dist/ - built files
8888
# bin/ - executable scripts
8989

90+
# Test site for demonstrations
91+
test-site/
92+
9093
# Ensure docs directory is included
9194
!docs/

src/services/api-service.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,23 +126,24 @@ export class ApiService {
126126
/**
127127
* Check if SHAs already exist on the server
128128
* @param {string[]} shas - Array of SHA256 hashes to check
129-
* @returns {Promise<string[]>} Array of existing SHAs
129+
* @param {string} buildId - Build ID for screenshot record creation
130+
* @returns {Promise<Object>} Response with existing SHAs and screenshot data
130131
*/
131-
async checkShas(shas) {
132+
async checkShas(shas, buildId) {
132133
try {
133134
const response = await this.request('/api/sdk/check-shas', {
134135
method: 'POST',
135136
headers: { 'Content-Type': 'application/json' },
136-
body: JSON.stringify({ shas }),
137+
body: JSON.stringify({ shas, buildId }),
137138
});
138-
return response.existing || [];
139+
return response;
139140
} catch (error) {
140141
// Continue without deduplication on error
141142
console.debug(
142143
'SHA check failed, continuing without deduplication:',
143144
error.message
144145
);
145-
return [];
146+
return { existing: [], missing: shas, screenshots: [] };
146147
}
147148
}
148149

@@ -159,14 +160,19 @@ export class ApiService {
159160
const sha256 = crypto.createHash('sha256').update(buffer).digest('hex');
160161

161162
// Check if this SHA already exists
162-
const existingShas = await this.checkShas([sha256]);
163+
const checkResult = await this.checkShas([sha256], buildId);
163164

164-
if (existingShas.includes(sha256)) {
165-
// File already exists, skip upload but still register the screenshot
165+
if (checkResult.existing && checkResult.existing.includes(sha256)) {
166+
// File already exists, screenshot record was automatically created
167+
const screenshot = checkResult.screenshots?.find(
168+
s => s.sha256 === sha256
169+
);
166170
return {
167171
message: 'Screenshot already exists, skipped upload',
168172
sha256,
169173
skipped: true,
174+
screenshot,
175+
fromExisting: true,
170176
};
171177
}
172178

src/services/uploader.js

Lines changed: 36 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,25 @@ export function createUploader(
9393
onProgress({ phase: 'processing', current, total: files.length })
9494
);
9595

96-
// Check which files need uploading
97-
const { toUpload, existing } = await checkExistingFiles(
96+
// Create build first to get buildId for SHA checking
97+
const buildInfo = {
98+
name: buildName || `Upload ${new Date().toISOString()}`,
99+
branch: branch || (await getDefaultBranch()) || 'main',
100+
commitSha: commit,
101+
commitMessage: message,
102+
environment,
103+
threshold,
104+
};
105+
106+
const build = await api.createBuild(buildInfo);
107+
const buildId = build.id;
108+
109+
// Check which files need uploading (now with buildId)
110+
const { toUpload, existing, screenshots } = await checkExistingFiles(
98111
fileMetadata,
99112
api,
100-
signal
113+
signal,
114+
buildId
101115
);
102116

103117
onProgress({
@@ -107,18 +121,13 @@ export function createUploader(
107121
total: files.length,
108122
});
109123

110-
// Create build and upload files
124+
// Upload remaining files
111125
const result = await uploadFiles({
112126
toUpload,
113127
existing,
114-
buildInfo: {
115-
name: buildName || `Upload ${new Date().toISOString()}`,
116-
branch: branch || (await getDefaultBranch()) || 'main',
117-
commitSha: commit,
118-
commitMessage: message,
119-
environment,
120-
threshold,
121-
},
128+
screenshots,
129+
buildId,
130+
buildInfo,
122131
api,
123132
signal,
124133
batchSize: batchSize,
@@ -277,9 +286,10 @@ async function processFiles(files, signal, onProgress) {
277286
/**
278287
* Check which files already exist on the server
279288
*/
280-
async function checkExistingFiles(fileMetadata, api, signal) {
289+
async function checkExistingFiles(fileMetadata, api, signal, buildId) {
281290
const allShas = fileMetadata.map(f => f.sha256);
282291
const existingShas = new Set();
292+
const allScreenshots = [];
283293

284294
// Check in batches
285295
for (let i = 0; i < allShas.length; i += DEFAULT_SHA_CHECK_BATCH_SIZE) {
@@ -291,11 +301,12 @@ async function checkExistingFiles(fileMetadata, api, signal) {
291301
const res = await api.request('/api/sdk/check-shas', {
292302
method: 'POST',
293303
headers: { 'Content-Type': 'application/json' },
294-
body: JSON.stringify({ shas: batch }),
304+
body: JSON.stringify({ shas: batch, buildId }),
295305
signal,
296306
});
297-
const { existing = [] } = res || {};
307+
const { existing = [], screenshots = [] } = res || {};
298308
existing.forEach(sha => existingShas.add(sha));
309+
allScreenshots.push(...screenshots);
299310
} catch (error) {
300311
// Continue without deduplication on error
301312
console.debug(
@@ -308,6 +319,7 @@ async function checkExistingFiles(fileMetadata, api, signal) {
308319
return {
309320
toUpload: fileMetadata.filter(f => !existingShas.has(f.sha256)),
310321
existing: fileMetadata.filter(f => existingShas.has(f.sha256)),
322+
screenshots: allScreenshots,
311323
};
312324
}
313325

@@ -316,53 +328,29 @@ async function checkExistingFiles(fileMetadata, api, signal) {
316328
*/
317329
async function uploadFiles({
318330
toUpload,
319-
existing,
320-
buildInfo,
331+
buildId,
321332
api,
322333
signal,
323334
batchSize,
324335
onProgress,
325336
}) {
326-
let buildId = null;
327337
let result = null;
328338

329-
// If all files exist, just create a build
339+
// If all files exist, screenshot records were already created during SHA check
330340
if (toUpload.length === 0) {
331-
return createBuildWithExisting({ existing, buildInfo, api, signal });
341+
return { buildId, url: null }; // Build was already created
332342
}
333343

334344
// Upload in batches
335345
for (let i = 0; i < toUpload.length; i += batchSize) {
336346
if (signal.aborted) throw new UploadError('Operation cancelled');
337347

338348
const batch = toUpload.slice(i, i + batchSize);
339-
const isFirstBatch = i === 0;
340349

341350
const form = new FormData();
342351

343-
if (isFirstBatch) {
344-
// First batch creates the build
345-
form.append('build_name', buildInfo.name);
346-
form.append('branch', buildInfo.branch);
347-
form.append('environment', buildInfo.environment);
348-
349-
if (buildInfo.commitSha) form.append('commit_sha', buildInfo.commitSha);
350-
if (buildInfo.commitMessage)
351-
form.append('commit_message', buildInfo.commitMessage);
352-
if (buildInfo.threshold !== undefined)
353-
form.append('threshold', buildInfo.threshold.toString());
354-
355-
// Include existing SHAs
356-
if (existing.length > 0) {
357-
form.append(
358-
'existing_shas',
359-
JSON.stringify(existing.map(f => f.sha256))
360-
);
361-
}
362-
} else {
363-
// Subsequent batches add to existing build
364-
form.append('build_id', buildId);
365-
}
352+
// All batches add to existing build (build was created earlier)
353+
form.append('build_id', buildId);
366354

367355
// Add files
368356
for (const file of batch) {
@@ -383,52 +371,17 @@ async function uploadFiles({
383371
});
384372
}
385373

386-
if (isFirstBatch && result.build?.id) {
387-
buildId = result.build.id;
388-
}
389-
390374
onProgress(i + batch.length);
391375
}
392376

393377
return {
394-
buildId: result.build?.id || buildId,
395-
url: result.build?.url || result.url,
378+
buildId,
379+
url: result?.build?.url || result?.url,
396380
};
397381
}
398382

399-
/**
400-
* Create a build with only existing files
401-
*/
402-
async function createBuildWithExisting({ existing, buildInfo, api, signal }) {
403-
const form = new FormData();
404-
405-
form.append('build_name', buildInfo.name);
406-
form.append('branch', buildInfo.branch);
407-
form.append('environment', buildInfo.environment);
408-
form.append('existing_shas', JSON.stringify(existing.map(f => f.sha256)));
409-
410-
if (buildInfo.commitSha) form.append('commit_sha', buildInfo.commitSha);
411-
if (buildInfo.commitMessage)
412-
form.append('commit_message', buildInfo.commitMessage);
413-
if (buildInfo.threshold !== undefined)
414-
form.append('threshold', buildInfo.threshold.toString());
415-
416-
let result;
417-
try {
418-
result = await api.request('/api/sdk/upload', {
419-
method: 'POST',
420-
body: form,
421-
signal,
422-
headers: {},
423-
});
424-
} catch (err) {
425-
throw new UploadError(`Failed to create build: ${err.message}`);
426-
}
427-
return {
428-
buildId: result.build?.id,
429-
url: result.build?.url || result.url,
430-
};
431-
}
383+
// createBuildWithExisting function removed - no longer needed since
384+
// builds are created first and /check-shas automatically creates screenshot records
432385

433386
/**
434387
* Uploader class for handling screenshot uploads

0 commit comments

Comments
 (0)