Skip to content

Commit ccf9f78

Browse files
authored
Take and populate screenshot (#114)
* screenshots-prop * broken_links.spec working * fix naming * pass-args * response to comments
1 parent dbcc3f2 commit ccf9f78

16 files changed

+656
-262
lines changed

package-lock.json

+18-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Binary file not shown.

packages/synthetics-sdk-broken-links/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
"@types/chai": "^4.3.4",
2828
"@types/express": "^4.17.17",
2929
"@types/node": "^18.15.10",
30+
"@types/proxyquire": "^1.3.31",
3031
"@types/sinon": "^10.0.16",
3132
"@types/supertest": "^2.0.12",
3233
"chai": "^4.3.7",
3334
"chai-exclude": "^2.1.0",
3435
"express": "^4.18.2",
3536
"proxyquire": "^2.1.3",
37+
"node-mocks-http": "^1.13.0",
3638
"sinon": "^16.1.1",
3739
"supertest": "^6.3.3",
3840
"synthetics-sdk-broken-links": "file:./"

packages/synthetics-sdk-broken-links/src/broken_links.ts

+32-15
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { processOptions } from './options_func';
4040
import {
4141
createStorageClientIfStorageSelected,
4242
getOrCreateStorageBucket,
43+
StorageParameters,
4344
} from './storage_func';
4445

4546
// External Dependencies
@@ -100,7 +101,11 @@ try {
100101
instantiateMetadata(synthetics_sdk_broken_links_package);
101102

102103
export async function runBrokenLinks(
103-
inputOptions: BrokenLinkCheckerOptions
104+
inputOptions: BrokenLinkCheckerOptions,
105+
args: {
106+
executionId: string | undefined;
107+
checkId: string | undefined;
108+
}
104109
): Promise<SyntheticResult> {
105110
// init
106111
const startTime = new Date().toISOString();
@@ -112,6 +117,11 @@ export async function runBrokenLinks(
112117
const adjusted_synthetic_timeout_millis =
113118
options.total_synthetic_timeout_millis! - 7000;
114119

120+
// Create Promise and variables used to set and resolve the time limit
121+
// imposed by `adjusted_synthetic_timeout`
122+
const [timeLimitPromise, timeLimitTimeout, timeLimitresolver] =
123+
getTimeLimitPromise(startTime, adjusted_synthetic_timeout_millis);
124+
115125
const errors: BaseError[] = [];
116126

117127
// Initialize Storage Client with Error Handling. Set to `null` if
@@ -121,18 +131,20 @@ export async function runBrokenLinks(
121131
options.screenshot_options!.capture_condition
122132
);
123133

124-
// TODO. Just to show where this will be called. uncommented in next PR
125-
// Bucket Validation
126-
// const bucket: Bucket | null = await getOrCreateStorageBucket(
127-
// storageClient,
128-
// options.screenshot_options!.storage_location,
129-
// errors
130-
// );
134+
// // Bucket Validation
135+
const bucket: Bucket | null = await getOrCreateStorageBucket(
136+
storageClient,
137+
options.screenshot_options!.storage_location,
138+
errors
139+
);
131140

132-
// Create Promise and variables used to set and resolve the time limit
133-
// imposed by `adjusted_synthetic_timeout`
134-
const [timeLimitPromise, timeLimitTimeout, timeLimitresolver] =
135-
getTimeLimitPromise(startTime, adjusted_synthetic_timeout_millis);
141+
const storageParams: StorageParameters = {
142+
storageClient: storageClient,
143+
bucket: bucket,
144+
checkId: args.checkId || '_',
145+
executionId: args.executionId || '_',
146+
screenshotNumber: 1,
147+
};
136148

137149
const followed_links: BrokenLinksResultV1_SyntheticLinkResult[] = [];
138150

@@ -147,7 +159,8 @@ export async function runBrokenLinks(
147159
originPage,
148160
options,
149161
startTime,
150-
adjusted_synthetic_timeout_millis
162+
adjusted_synthetic_timeout_millis,
163+
storageParams
151164
)
152165
);
153166

@@ -169,7 +182,8 @@ export async function runBrokenLinks(
169182
linksToFollow,
170183
options,
171184
startTime,
172-
adjusted_synthetic_timeout_millis
185+
adjusted_synthetic_timeout_millis,
186+
storageParams
173187
))
174188
);
175189
return true;
@@ -188,6 +202,7 @@ export async function runBrokenLinks(
188202
runtime_metadata,
189203
options,
190204
followed_links,
205+
storageParams,
191206
errors
192207
);
193208
} catch (err) {
@@ -215,7 +230,8 @@ async function checkOriginLink(
215230
originPage: Page,
216231
options: BrokenLinksResultV1_BrokenLinkCheckerOptions,
217232
startTime: string,
218-
adjusted_synthetic_timeout_millis: number
233+
adjusted_synthetic_timeout_millis: number,
234+
storageParams: StorageParameters
219235
): Promise<BrokenLinksResultV1_SyntheticLinkResult> {
220236
let originLinkResult: BrokenLinksResultV1_SyntheticLinkResult;
221237

@@ -232,6 +248,7 @@ async function checkOriginLink(
232248
originPage,
233249
{ target_uri: options.origin_uri, anchor_text: '', html_element: '' },
234250
options,
251+
storageParams,
235252
true
236253
);
237254

packages/synthetics-sdk-broken-links/src/handlers.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import { Request, Response } from 'express';
1818
// Internal Project Files
1919
import { runBrokenLinks, BrokenLinkCheckerOptions } from './broken_links';
2020

21+
const syntheticExecutionIdHeader = 'Synthetic-Execution-Id';
22+
const checkIdHeader = 'Check-Id';
23+
2124
/**
2225
* Middleware for easy invocation of SyntheticSDK broken links, and may be used to
2326
* register a GoogleCloudFunction http function, or express js compatible handler.
@@ -29,5 +32,10 @@ import { runBrokenLinks, BrokenLinkCheckerOptions } from './broken_links';
2932
export function runBrokenLinksHandler(options: BrokenLinkCheckerOptions) {
3033
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3134
return async (req: Request, res: Response): Promise<any> =>
32-
res.send(await runBrokenLinks(options));
35+
res.send(
36+
await runBrokenLinks(options, {
37+
executionId: req.get(syntheticExecutionIdHeader),
38+
checkId: req.get(checkIdHeader),
39+
})
40+
);
3341
}

packages/synthetics-sdk-broken-links/src/link_utils.ts

+65-1
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
// Standard Libraries
16+
import * as path from 'path';
17+
1518
// Internal Project Files
1619
import {
1720
BaseError,
1821
BrokenLinksResultV1,
1922
BrokenLinksResultV1_BrokenLinkCheckerOptions,
2023
BrokenLinksResultV1_BrokenLinkCheckerOptions_LinkOrder,
24+
BrokenLinksResultV1_BrokenLinkCheckerOptions_ScreenshotOptions_CaptureCondition as ApiCaptureCondition,
2125
BrokenLinksResultV1_SyntheticLinkResult,
2226
GenericResultV1,
2327
getRuntimeMetadata,
@@ -28,6 +32,7 @@ import {
2832

2933
// External Dependencies
3034
import { HTTPResponse } from 'puppeteer';
35+
import { StorageParameters } from './storage_func';
3136

3237
/**
3338
* Represents an intermediate link with its properties.
@@ -157,7 +162,7 @@ function parseFollowedLinks(
157162
options: {} as BrokenLinksResultV1_BrokenLinkCheckerOptions,
158163
origin_link_result: {} as BrokenLinksResultV1_SyntheticLinkResult,
159164
followed_link_results: [],
160-
execution_data_storage_path: '', // TODO: make sure that when this is set it begins with gs://
165+
execution_data_storage_path: '',
161166
errors: [],
162167
};
163168

@@ -223,13 +228,20 @@ export function createSyntheticResult(
223228
runtime_metadata: { [key: string]: string },
224229
options: BrokenLinksResultV1_BrokenLinkCheckerOptions,
225230
followed_links: BrokenLinksResultV1_SyntheticLinkResult[],
231+
storageParams: StorageParameters,
226232
errors: BaseError[]
227233
): SyntheticResult {
228234
// Create BrokenLinksResultV1 by parsing followed links and setting options
229235
const broken_links_result: BrokenLinksResultV1 =
230236
parseFollowedLinks(followed_links);
231237
broken_links_result.options = options;
232238
broken_links_result.errors = errors;
239+
broken_links_result.execution_data_storage_path = storageParams.bucket
240+
? 'gs://' +
241+
storageParams.bucket.name +
242+
'/' +
243+
getStoragePathToExecution(storageParams, options)
244+
: '';
233245

234246
// Create SyntheticResult object
235247
const synthetic_result: SyntheticResult = {
@@ -273,6 +285,26 @@ export function shuffleAndTruncate(
273285
}
274286

275287
/**
288+
* Determines whether a screenshot should be taken based on screenshot options and link result.
289+
*
290+
* @param options - BrokenLinksResultV1_BrokenLinkCheckerOptions
291+
* @param passed - boolean indicating whether the link navigation succeeded
292+
* @returns true if a screenshot should be taken, false otherwise
293+
*/
294+
export function shouldTakeScreenshot(
295+
options: BrokenLinksResultV1_BrokenLinkCheckerOptions,
296+
passed: boolean
297+
): boolean {
298+
return (
299+
options.screenshot_options!.capture_condition === ApiCaptureCondition.ALL ||
300+
(options.screenshot_options!.capture_condition ===
301+
ApiCaptureCondition.FAILING &&
302+
!passed)
303+
);
304+
}
305+
306+
/**
307+
276308
* Sanitizes an object name string for safe use, ensuring compliance with
277309
* naming restrictions.
278310
*
@@ -303,6 +335,38 @@ export function sanitizeObjectName(
303335
.replace(/\s+/g, '_'); // Replace one or more spaces with underscores
304336
}
305337

338+
export function getStoragePathToExecution(
339+
storageParams: StorageParameters,
340+
options: BrokenLinksResultV1_BrokenLinkCheckerOptions
341+
) {
342+
try {
343+
const storageLocation = options.screenshot_options!.storage_location;
344+
let writeDestination = '';
345+
346+
// extract folder name for a given storage location. If there is no '/'
347+
// present then the storageLocation is just a folder
348+
const firstSlashIndex = storageLocation.indexOf('/');
349+
if (firstSlashIndex !== -1) {
350+
writeDestination = storageLocation.substring(firstSlashIndex + 1);
351+
}
352+
353+
// Ensure writeDestination ends with a slash for proper path joining
354+
if (writeDestination && !writeDestination.endsWith('/')) {
355+
writeDestination += '/';
356+
}
357+
358+
writeDestination = path.join(
359+
writeDestination,
360+
storageParams.checkId,
361+
storageParams.executionId
362+
);
363+
364+
return writeDestination;
365+
} catch (err) {
366+
return '';
367+
}
368+
}
369+
306370
export function getTimeLimitPromise(
307371
startTime: string,
308372
totalTimeoutMillis: number,

0 commit comments

Comments
 (0)